# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id: Report.pm 12133 2015-05-01 04:39:09Z sikeda $

# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015 GIP RENATER
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

package Sympa::Report;

use strict;
use warnings;

use Sympa;
use Sympa::Bulk;
use Sympa::Log;
use Sympa::Message;

my $log = Sympa::Log->instance;

### MESSAGE DIFFUSION REPORT ###

############################################################
#  reject_report_msg
############################################################
#  Send a notification to the user about an error rejecting
#  its message diffusion, using mail_tt2/message_report.tt2
#
# IN : -$type (+): 'intern'||'intern_quiet'||'user'||auth' - the error type
#      -$error : scalar - the entry in message_report.tt2 if $type = 'user'
#                       - string error for listmaster if $type = 'intern'
#                       - the entry in authorization reject (called by
#                       message_report.tt2)
#                               if $type = 'auth'
#      -$user (+): scalar - the user to notify
#      -$param : ref(HASH) - var used in message_report.tt2
#         $param->msg_id (+) if $type='intern'
#      -$robot (+): robot
#      -$msg_string : string - rejected msg
#      -$list : ref(List)
#
# OUT : 1
#
##############################################################
sub reject_report_msg {
    my ($type, $error, $user, $param, $robot, $msg_string, $list) = @_;
    $log->syslog('debug2', '(%s, %s, %s)', $type, $error, $user);

    undef $list unless ref $list eq 'Sympa::List';

    unless ($type eq 'intern'
        or $type eq 'intern_quiet'
        or $type eq 'user'
        or $type eq 'auth'
        or $type eq 'plugin') {
        $log->syslog(
            'err',
            'Error to prepare parsing "message_report" template to %s: Not a valid error type',
            $user
        );
        return undef;
    }

    unless ($user) {
        $log->syslog('err',
            'Unable to send template "command_report": No user to notify');
        return undef;
    }

    unless ($robot) {
        $log->syslog('err',
            'Unable to send template "command_report": No robot');
        return undef;
    }

    chomp($user);
    $param->{'to'}             = $user;
    $param->{'msg'}            = $msg_string;
    $param->{'auto_submitted'} = 'auto-replied';
    $param->{'entry'}          = $error;

    $param->{'type'} =
          $type eq 'user'   ? 'user_error'
        : $type eq 'auth'   ? 'authorization_reject'
        : $type eq 'plugin' ? 'plugin'
        :                     'intern_error';

    ## Prepare the original message if provided
    if (defined $param->{'message'}) {
        $param->{'original_msg'} = _get_msg_as_hash($param->{'message'});
    }

    my $report_message = Sympa::Message->new_from_template(($list || $robot),
        'message_report', $user, $param);
    if ($report_message) {
        # Ensure 1 second elapsed since last message
        $report_message->{'date'} = time + 1;
    }
    unless ($report_message
        and defined Sympa::Bulk->new->store($report_message, $user)) {
        $log->syslog('notice',
            'Unable to send template "message_report" to "%s"', $user);
    }

    if ($type eq 'intern') {
        chomp($param->{'msg_id'});

        $param ||= {};
        $param->{'error'}  = $error;
        $param->{'who'}    = $user;
        $param->{'action'} = 'message diffusion';
        $param->{'msg_id'} = $param->{'msg_id'};
        Sympa::send_notify_to_listmaster(($list || $robot),
            'mail_intern_error', $param);
    }
    return 1;
}

############################################################
#  _get_msg_as_hash
############################################################
#  Internal subroutine
#  Provide useful parts of a message as a hash entries
#
# IN : -$msg_object (+): ref(HASH) - the MIME::Entity or Message object
#
# OUT : $msg_hash : ref(HASH) - the hashref
#
##############################################################
#FIXME: Is returned value by this function actually used?

sub _get_msg_as_hash {
    my $msg_object = shift;

    my ($msg_entity, $msg_string, $msg_hash);

    if ($msg_object->isa('MIME::Entity')) {    ## MIME-Tools object
        $msg_entity = $msg_object;
        $msg_string = $msg_entity->as_string;
    } elsif (ref $msg_object eq 'Sympa::Message') {
        # Sympa's own Message object
        $msg_entity = $msg_object->as_entity;
        $msg_string = $msg_object->as_string;
    } else {
        $log->syslog('err', 'Wrong type for msg parameter');
    }

    my $head        = $msg_entity->head;
    my $body_handle = $msg_entity->bodyhandle;
    my $body_as_string;

    if (defined $body_handle) {
        $body_as_string = $body_handle->as_lines();
    }

    ## TODO : we should also decode headers + remove trailing \n + use these
    ## variables in default mail templates

    my $from    = $head->get('From');
    my $subject = $head->get('Subject');
    my $msg_id  = $head->get('Message-Id');
    $msg_hash = {
        'full'       => $msg_string,
        'body'       => $body_as_string,
        'from'       => $from,
        'subject'    => $subject,
        'message_id' => $msg_id
    };

    return $msg_hash;
}

############################################################
#  notice_report_msg
############################################################
#  Send a notification to the user about a success for its
#   message diffusion, using mail_tt2/message_report.tt2
#
# IN : -$entry (+): scalar - the entry in message_report.tt2
#      -$user (+): scalar - the user to notify
#      -$param : ref(HASH) - var used in message_report.tt2
#      -$robot (+) : robot
#      -$list : ref(List)
#
# OUT : 1
#
##############################################################
sub notice_report_msg {
    my ($entry, $user, $param, $robot, $list) = @_;

    undef $list unless ref $list eq 'Sympa::List';

    $param->{'to'}             = $user;
    $param->{'type'}           = 'success';
    $param->{'entry'}          = $entry;
    $param->{'auto_submitted'} = 'auto-replied';

    unless ($user) {
        $log->syslog('err',
            'Unable to send template "message_report": No user to notify');
        return undef;
    }

    unless ($robot) {
        $log->syslog('err',
            'Unable to send template "message_report": No robot');
        return undef;
    }

    ## Prepare the original message if provided
    if (defined $param->{'message'}) {
        $param->{'original_msg'} = _get_msg_as_hash($param->{'message'});
    }

    my $report_message = Sympa::Message->new_from_template(($list || $robot),
        'message_report', $user, $param);
    if ($report_message) {
        # Ensure 1 second elapsed since last message
        $report_message->{'date'} = time + 1;
    }
    unless ($report_message
        and defined Sympa::Bulk->new->store($report_message, $user)) {
        $log->syslog('notice',
            'Unable to send template "message_report" to "%s"', $user);
    }

    return 1;
}

### MAIL COMMAND REPORT ###

# for rejected command because of internal error
my @intern_error_cmd;
# for rejected command because of user error
my @user_error_cmd;
# for errors no relative to a command
my @global_error_cmd;
# for rejected command because of no authorization
my @auth_reject_cmd;
# for command notice
my @notice_cmd;

#########################################################
# init_report_cmd
#########################################################
#  init arrays for mail command reports :
#
#
# IN : -
#
# OUT : -
#
#########################################################
sub init_report_cmd {

    undef @intern_error_cmd;
    undef @user_error_cmd;
    undef @global_error_cmd;
    undef @auth_reject_cmd;
    undef @notice_cmd;
}

#########################################################
# is_there_any_report_cmd
#########################################################
#  Look for some mail command report in one of arrays report
#
# IN : -
#
# OUT : 1 if there are some reports to send
#
#########################################################
sub is_there_any_report_cmd {

    return (   @intern_error_cmd
            || @user_error_cmd
            || @global_error_cmd
            || @auth_reject_cmd
            || @notice_cmd);
}

#########################################################
# send_report_cmd
#########################################################
#  Send the template command_report to $sender
#   with global arrays :
#  @intern_error_cmd,@user_error_cmd,@global_error_cmd,
#   @auth_reject_cmd,@notice_cmd.
#
#
# IN : -$sender (+): SCALAR
#      -$robot (+): SCALAR
#
# OUT : 1 if there are some reports to send
#
#########################################################
sub send_report_cmd {
    my ($sender, $robot) = @_;

    unless ($sender) {
        $log->syslog('err',
            'Unable to send template "command_report": No user to notify');
        return undef;
    }

    unless ($robot) {
        $log->syslog('err',
            'Unable to send template "command_report": No robot');
        return undef;
    }

    # for mail layout
    my $before_auth = 0;
    $before_auth = 1 if ($#notice_cmd + 1);

    my $before_user_err;
    $before_user_err = 1 if ($before_auth || ($#auth_reject_cmd + 1));

    my $before_intern_err;
    $before_intern_err = 1 if ($before_user_err || ($#user_error_cmd + 1));

    chomp($sender);

    my $data = {
        'to'                => $sender,
        'nb_notice'         => $#notice_cmd + 1,
        'nb_auth'           => $#auth_reject_cmd + 1,
        'nb_user_err'       => $#user_error_cmd + 1,
        'nb_intern_err'     => $#intern_error_cmd + 1,
        'nb_global'         => $#global_error_cmd + 1,
        'before_auth'       => $before_auth,
        'before_user_err'   => $before_user_err,
        'before_intern_err' => $before_intern_err,
        'notices'           => \@notice_cmd,
        'auths'             => \@auth_reject_cmd,
        'user_errors'       => \@user_error_cmd,
        'intern_errors'     => \@intern_error_cmd,
        'globals'           => \@global_error_cmd,
    };

    my $report_message =
        Sympa::Message->new_from_template($robot, 'command_report', $sender,
        $data);
    if ($report_message) {
        # Ensure 1 second elapsed since last message
        $report_message->{'date'} = time + 1;
    }
    unless ($report_message
        and defined Sympa::Bulk->new->store($report_message, $sender)) {
        $log->syslog('notice',
            'Unable to send template "command_report" to %s', $sender);
    }

    init_report_cmd();
}

#########################################################
# global_report_cmd
#########################################################
#  puts global report of mail with commands in
#  @global_report_cmd  used to send message with template
#  mail_tt2/command_report.tt2
#  if $now , the template is sent now
#  if $type eq 'intern', the listmaster is notified
#
# IN : -$type (+): 'intern'||'intern_quiet||'user'
#      -$error : scalar - $glob.entry in command_report.tt2 if $type = 'user'
#                          - string error for listmaster if $type = 'intern'
#      -$data : ref(HASH) - var used in command_report.tt2
#      -$sender :  required if $type eq 'intern' or if $now
#                  scalar - the user to notify
#      -$robot :   required if $type eq 'intern' or if $now
#                  scalar - to notify useror listmaster
#      -$now : send now if true
#
# OUT : 1|| undef
#
#########################################################
sub global_report_cmd {
    my ($type, $error, $data, $sender, $robot, $now) = @_;
    my $entry;

    unless ($type eq 'intern' || $type eq 'intern_quiet' || $type eq 'user') {
        $log->syslog(
            'err',
            'Error to prepare parsing "command_report" template to %s: Not a valid error type',
            $sender
        );
        return undef;
    }

    if ($type eq 'intern') {

        if ($robot) {
            my $param = $data;
            $param ||= {};
            $param->{'error'}  = $error;
            $param->{'who'}    = $sender;
            $param->{'action'} = 'Command process';

            Sympa::send_notify_to_listmaster($robot, 'mail_intern_error',
                $param);
        } else {
            $log->syslog('notice',
                'Unable to send notify to listmaster: No robot');
        }
    }

    if ($type eq 'user') {
        $entry = $error;

    } else {
        $entry = 'intern_error';
    }

    $data ||= {};
    $data->{'entry'} = $entry;
    push @global_error_cmd, $data;

    if ($now) {
        unless ($sender && $robot) {
            $log->syslog('err',
                'Unable to send template "command_report" now: No sender or robot'
            );
            return undef;
        }
        send_report_cmd($sender, $robot);

    }
}

#########################################################
# reject_report_cmd
#########################################################
#  puts errors reports of processed commands in
#  @user/intern_error_cmd, @auth_reject_cmd
#  used to send message with template
#  mail_tt2/command_report.tt2
#  if $type eq 'intern', the listmaster is notified
#
# IN : -$type (+): 'intern'||'intern_quiet||'user'||'auth'
#      -$error : scalar - $u_err.entry in command_report.tt2 if $type = 'user'
#                       - $auth.entry in command_report.tt2 if $type = 'auth'
#                       - string error for listmaster if $type = 'intern'
#      -$data : ref(HASH) - var used in command_report.tt2
#      -$cmd : SCALAR - the rejected cmd : $xx.cmd in command_report.tt2
#      -$sender :  required if $type eq 'intern'
#                  scalar - the user to notify
#      -$robot :   required if $type eq 'intern'
#                  scalar - to notify listmaster
#
# OUT : 1|| undef
#
#########################################################
sub reject_report_cmd {
    my ($type, $error, $data, $cmd, $sender, $robot) = @_;

    unless ($type eq 'intern'
        || $type eq 'intern_quiet'
        || $type eq 'user'
        || $type eq 'auth') {
        $log->syslog(
            'err',
            'Error to prepare parsing "command_report" template to %s: Not a valid error type',
            $sender
        );
        return undef;
    }

    if ($type eq 'intern') {
        if ($robot) {

            my $listname;
            if (defined $data->{'listname'}) {
                $listname = $data->{'listname'};
            }

            my $param = $data;
            $param ||= {};
            $param->{'error'}    = $error;
            $param->{'cmd'}      = $cmd;
            $param->{'listname'} = $listname;
            $param->{'who'}      = $sender;
            $param->{'action'}   = 'Command process';

            Sympa::send_notify_to_listmaster($robot, 'mail_intern_error',
                $param);
        } else {
            $log->syslog('notice',
                'Unable to notify listmaster for error: "%s": (no robot)',
                $error);
        }
    }

    $data ||= {};
    $data->{'cmd'} = $cmd;

    if ($type eq 'auth') {
        $data->{'entry'} = $error;
        push @auth_reject_cmd, $data;

    } elsif ($type eq 'user') {
        $data->{'entry'} = $error;
        push @user_error_cmd, $data;

    } else {
        $data->{'entry'} = 'intern_error';
        push @intern_error_cmd, $data;

    }

}

#########################################################
# notice_report_cmd
#########################################################
#  puts notices reports of processed commands in
#  @notice_cmd used to send message with template
#  mail_tt2/command_report.tt2
#
# IN : -$entry : $notice.entry to select string in
#               command_report.tt2
#      -$data : ref(HASH) - var used in command_report.tt2
#      -$cmd : SCALAR - the noticed cmd
#
# OUT : 1
#
#########################################################
sub notice_report_cmd {
    my ($entry, $data, $cmd) = @_;

    $data ||= {};
    $data->{'cmd'}   = $cmd;
    $data->{'entry'} = $entry;
    push @notice_cmd, $data;
}

### WEB COMMAND REPORT ###

# for rejected web command because of internal error
my @intern_error_web;
# for rejected web command because of system error
my @system_error_web;
# for rejected web command because of user error
my @user_error_web;
# for rejected web command because of no authorization
my @auth_reject_web;
# for web command notice
my @notice_web;

#########################################################
# init_report_web
#########################################################
#  init arrays for web reports :
#
#
# IN : -
#
# OUT : -
#
#########################################################
sub init_report_web {

    undef @intern_error_web;
    undef @system_error_web;
    undef @user_error_web;
    undef @auth_reject_web;
    undef @notice_web;
}

#########################################################
# is_there_any_reject_report_web
#########################################################
#  Look for some web reports in one of web
#  arrays reject report
#
# IN : -
#
# OUT : 1 if there are some reports to send
#
#########################################################
sub is_there_any_reject_report_web {

    return (   @intern_error_web
            || @system_error_web
            || @user_error_web
            || @auth_reject_web);
}

#########################################################
# get_intern_error_web
#########################################################
#  return array of web intern error
#
# IN : -
#
# OUT : ref(ARRAY) - clone of \@intern_error_web
#
#########################################################
sub get_intern_error_web {
    my @intern_err;

    foreach my $i (@intern_error_web) {
        push @intern_err, $i;
    }
    return \@intern_err;
}

#########################################################
# get_system_error_web
#########################################################
#  return array of web system error
#
# IN : -
#
# OUT : ref(ARRAY) - clone of \@system_error_web
#
#########################################################
sub get_system_error_web {
    my @system_err;

    foreach my $i (@system_error_web) {
        push @system_err, $i;
    }
    return \@system_err;
}

#########################################################
# get_user_error_web
#########################################################
#  return array of web user error
#
# IN : -
#
# OUT : ref(ARRAY) - clone of \@user_error_web
#
#########################################################
sub get_user_error_web {
    my @user_err;

    foreach my $u (@user_error_web) {
        push @user_err, $u;
    }
    return \@user_err;
}

#########################################################
# get_auth_reject_web
#########################################################
#  return array of web authorization rejects
#
# IN : -
#
# OUT : ref(ARRAY) - clone of \@auth_reject_web
#
#########################################################
sub get_auth_reject_web {
    my @auth_rej;

    foreach my $a (@auth_reject_web) {
        push @auth_rej, $a;
    }
    return \@auth_rej;
}

#########################################################
# get_notice_web
#########################################################
#  return array of web notice
#
# IN : -
#
# OUT : ref(ARRAY) - clone of \@notice_web
#
#########################################################
sub get_notice_web {
    my @notice;

    if (@notice_web) {

        foreach my $n (@notice_web) {
            push @notice, $n;
        }
        return \@notice;

    } else {
        return 0;
    }

}

#########################################################
# notice_report_web
#########################################################
#  puts notices reports of web commands in
#  @notice_web used to notice user with template
#  web_tt2/notice.tt2
#
# IN : -$msg : $notice.msg to select string in
#               web/notice.tt2
#      -$data : ref(HASH) - var used in web_tt2/notices.tt2
#      -$action : SCALAR - the noticed action $notice.action in
#      web_tt2/notices.tt2
#
# OUT : 1
#
#########################################################
sub notice_report_web {
    my ($msg, $data, $action) = @_;

    $data ||= {};
    $data->{'action'} = $action;
    $data->{'msg'}    = $msg;
    push @notice_web, $data;

}

#########################################################
# reject_report_web
#########################################################
#  puts errors reports of web commands in
#  @intern/user/system_error_web, @auth_reject_web
#   used to send message with template  web_tt2/error.tt2
#  if $type = 'intern'||'system', the listmaster is notified
#  (with 'web_intern_error' || 'web_system_error')
#
# IN : -$type (+):
# 'intern'||'intern_quiet||'system'||'system_quiet'||user'||'auth'
#      -$error (+): scalar  - $u_err.msg in error.tt2 if $type = 'user'
#                           - $auth.msg in error.tt2 if $type = 'auth'
#                           - $s_err.msg in error.tt2 if $type =
#                           'system'||'system_quiet'
#                           - $i_err.msg in error.tt2 if $type = 'intern' ||
#                           'intern_quiet'
#                           - $error in listmaster_notification if $type =
#                           'system'||'intern'
#      -$data : ref(HASH) - var used in web_tt2/error.tt2
#      -$action(+) : SCALAR - the rejected action :
#            $xx.action in web_tt2/error.tt2
#            $action in listmaster_notification.tt2 if needed
#      -$list : ref(List) || ''
#      -$user :  required if $type eq 'intern'||'system'
#                  scalar - the concerned user to notify listmaster
#      -$robot :   required if $type eq 'intern'||'system'
#                  scalar - the robot to notify listmaster
#
# OUT : 1|| undef
#
#########################################################
sub reject_report_web {
    my ($type, $error, $data, $action, $list, $user, $robot) = @_;

    unless ($type eq 'intern'
        || $type eq 'intern_quiet'
        || $type eq 'system'
        || $type eq 'system_quiet'
        || $type eq 'user'
        || $type eq 'auth') {
        $log->syslog(
            'err',
            'Error to prepare parsing "web_tt2/error.tt2" template to %s: Not a valid error type',
            $user
        );
        return undef;
    }

    my $listname;
    if (ref($list) eq 'Sympa::List') {
        $listname = $list->{'name'};
    }

    ## Notify listmaster for internal or system errors
    if ($type eq 'intern' || $type eq 'system') {
        if ($robot) {
            my $param = $data || {};
            $param->{'error'} = $error;
            $param->{'who'}   = $user;
            $param->{'action'} ||= 'Command process';

            Sympa::send_notify_to_listmaster(($list || $robot),
                'web_' . $type . '_error', $param);
        } else {
            $log->syslog('notice',
                'Unable to notify listmaster for error: "%s": (no robot)',
                $error);
        }
    }

    $data ||= {};

    $data->{'action'}   = $action;
    $data->{'msg'}      = $error;
    $data->{'listname'} = $listname;

    if ($type eq 'auth') {
        push @auth_reject_web, $data;

    } elsif ($type eq 'user') {
        push @user_error_web, $data;

    } elsif ($type eq 'system' || $type eq 'system_quiet') {
        push @system_error_web, $data;

    } elsif ($type eq 'intern' || $type eq 'intern_quiet') {
        push @intern_error_web, $data;

    }
}

1;
