# --
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --
package Kernel::System::PostMaster;
use strict;
use warnings;
use Kernel::System::EmailParser;
use Kernel::System::PostMaster::DestQueue;
use Kernel::System::PostMaster::NewTicket;
use Kernel::System::PostMaster::FollowUp;
use Kernel::System::PostMaster::Reject;
use Kernel::System::VariableCheck qw(IsHashRefWithData);
our %ObjectManagerFlags = (
NonSingleton => 1,
);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DynamicField',
'Kernel::System::Main',
'Kernel::System::Queue',
'Kernel::System::State',
'Kernel::System::Ticket',
'Kernel::System::Ticket::Article',
);
=head1 NAME
Kernel::System::PostMaster - postmaster lib
=head1 DESCRIPTION
All postmaster functions. E. g. to process emails.
=head1 PUBLIC INTERFACE
=head2 new()
Don't use the constructor directly, use the ObjectManager instead:
my $PostMasterObject = $Kernel::OM->Create(
'Kernel::System::PostMaster',
ObjectParams => {
Email => \@ArrayOfEmailContent,
Trusted => 1, # 1|0 ignore X-OTRS header if false
},
);
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# check needed objects
$Self->{Email} = $Param{Email} || die "Got no Email!";
$Self->{CommunicationLogObject} = $Param{CommunicationLogObject} || die "Got no CommunicationLogObject!";
$Self->{ParserObject} = Kernel::System::EmailParser->new(
Email => $Param{Email},
);
# create needed objects
$Self->{DestQueueObject} = Kernel::System::PostMaster::DestQueue->new( %{$Self} );
$Self->{NewTicketObject} = Kernel::System::PostMaster::NewTicket->new( %{$Self} );
$Self->{FollowUpObject} = Kernel::System::PostMaster::FollowUp->new( %{$Self} );
$Self->{RejectObject} = Kernel::System::PostMaster::Reject->new( %{$Self} );
# get config object
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# check needed config options
for my $Option (qw(PostmasterUserID PostmasterX-Header)) {
$Self->{$Option} = $ConfigObject->Get($Option)
|| die "Found no '$Option' option in configuration!";
}
# should I use x-otrs headers?
$Self->{Trusted} = defined $Param{Trusted} ? $Param{Trusted} : 1;
if ( $Self->{Trusted} ) {
# get dynamic field objects
my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
# add Dynamic Field headers
my $DynamicFields = $DynamicFieldObject->DynamicFieldList(
Valid => 1,
ObjectType => [ 'Ticket', 'Article' ],
ResultType => 'HASH',
);
# create a lookup table
my %HeaderLookup = map { $_ => 1 } @{ $Self->{'PostmasterX-Header'} };
for my $DynamicField ( values %$DynamicFields ) {
for my $Header (
'X-OTRS-DynamicField-' . $DynamicField,
'X-OTRS-FollowUp-DynamicField-' . $DynamicField,
)
{
# only add the header if is not alreday in the config
if ( !$HeaderLookup{$Header} ) {
push @{ $Self->{'PostmasterX-Header'} }, $Header;
}
}
}
}
return $Self;
}
=head2 Run()
to execute the run process
$PostMasterObject->Run(
Queue => 'Junk', # optional, specify target queue for new tickets
QueueID => 1, # optional, specify target queue for new tickets
);
return params
0 = error (also false)
1 = new ticket created
2 = follow up / open/reopen
3 = follow up / close -> new ticket
4 = follow up / close -> reject
5 = ignored (because of X-OTRS-Ignore header)
=cut
sub Run {
my ( $Self, %Param ) = @_;
my @Return;
# ConfigObject section / get params
my $GetParam = $Self->GetEmailParams();
# check if follow up
my ( $Tn, $TicketID ) = $Self->CheckFollowUp( GetParam => $GetParam );
# get config objects
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# run all PreFilterModules (modify email params)
if ( ref $ConfigObject->Get('PostMaster::PreFilterModule') eq 'HASH' ) {
my %Jobs = %{ $ConfigObject->Get('PostMaster::PreFilterModule') };
# get main objects
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
JOB:
for my $Job ( sort keys %Jobs ) {
return if !$MainObject->Require( $Jobs{$Job}->{Module} );
my $FilterObject = $Jobs{$Job}->{Module}->new(
%{$Self},
);
if ( !$FilterObject ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Error',
Key => 'Kernel::System::PostMaster',
Value => "new() of PreFilterModule $Jobs{$Job}->{Module} not successfully!",
);
next JOB;
}
# modify params
my $Run = $FilterObject->Run(
GetParam => $GetParam,
JobConfig => $Jobs{$Job},
TicketID => $TicketID,
UserID => $Self->{PostmasterUserID},
);
if ( !$Run ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Error',
Key => 'Kernel::System::PostMaster',
Value => "Execute Run() of PreFilterModule $Jobs{$Job}->{Module} not successfully!",
);
}
}
}
# should I ignore the incoming mail?
if ( $GetParam->{'X-OTRS-Ignore'} && $GetParam->{'X-OTRS-Ignore'} =~ /(yes|true)/i ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Info',
Key => 'Kernel::System::PostMaster',
Value =>
"Ignored Email (From: $GetParam->{'From'}, Message-ID: $GetParam->{'Message-ID'}) "
. "because the X-OTRS-Ignore is set (X-OTRS-Ignore: $GetParam->{'X-OTRS-Ignore'}).",
);
return (5);
}
#
# ticket section
#
# check if follow up (again, with new GetParam)
( $Tn, $TicketID ) = $Self->CheckFollowUp( GetParam => $GetParam );
# run all PreCreateFilterModules
if ( ref $ConfigObject->Get('PostMaster::PreCreateFilterModule') eq 'HASH' ) {
my %Jobs = %{ $ConfigObject->Get('PostMaster::PreCreateFilterModule') };
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
JOB:
for my $Job ( sort keys %Jobs ) {
return if !$MainObject->Require( $Jobs{$Job}->{Module} );
my $FilterObject = $Jobs{$Job}->{Module}->new(
%{$Self},
);
if ( !$FilterObject ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Error',
Key => 'Kernel::System::PostMaster',
Value => "new() of PreCreateFilterModule $Jobs{$Job}->{Module} not successfully!",
);
next JOB;
}
# modify params
my $Run = $FilterObject->Run(
GetParam => $GetParam,
JobConfig => $Jobs{$Job},
TicketID => $TicketID,
UserID => $Self->{PostmasterUserID},
);
if ( !$Run ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Error',
Key => 'Kernel::System::PostMaster',
Value => "Execute Run() of PreCreateFilterModule $Jobs{$Job}->{Module} not successfully!",
);
}
}
}
# check if it's a follow up ...
if ( $Tn && $TicketID ) {
# get ticket object
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
# get ticket data
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
DynamicFields => 0,
);
# get queue object
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
# check if it is possible to do the follow up
# get follow up option (possible or not)
my $FollowUpPossible = $QueueObject->GetFollowUpOption(
QueueID => $Ticket{QueueID},
);
# get lock option (should be the ticket locked - if closed - after the follow up)
my $Lock = $QueueObject->GetFollowUpLockOption(
QueueID => $Ticket{QueueID},
);
# get state details
my %State = $Kernel::OM->Get('Kernel::System::State')->StateGet(
ID => $Ticket{StateID},
);
# Check if we need to treat a bounce e-mail always as a normal follow-up (to reopen the ticket if needed).
my $BounceEmailAsFollowUp = 0;
if ( $GetParam->{'X-OTRS-Bounce'} ) {
$BounceEmailAsFollowUp = $ConfigObject->Get('PostmasterBounceEmailAsFollowUp');
}
# create a new ticket
if ( !$BounceEmailAsFollowUp && $FollowUpPossible =~ /new ticket/i && $State{TypeName} =~ /^(removed|close)/i )
{
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Info',
Key => 'Kernel::System::PostMaster',
Value => "Follow up for [$Tn] but follow up not possible ($Ticket{State}). Create new ticket.",
);
# send mail && create new article
# get queue if of From: and To:
if ( !$Param{QueueID} ) {
$Param{QueueID} = $Self->{DestQueueObject}->GetQueueID(
Params => $GetParam,
);
}
# check if trusted returns a new queue id
my $TQueueID = $Self->{DestQueueObject}->GetTrustedQueueID(
Params => $GetParam,
);
if ($TQueueID) {
$Param{QueueID} = $TQueueID;
}
# Clean out the old TicketNumber from the subject (see bug#9108).
# This avoids false ticket number detection on customer replies.
if ( $GetParam->{Subject} ) {
$GetParam->{Subject} = $TicketObject->TicketSubjectClean(
TicketNumber => $Tn,
Subject => $GetParam->{Subject},
);
}
$TicketID = $Self->{NewTicketObject}->Run(
InmailUserID => $Self->{PostmasterUserID},
GetParam => $GetParam,
QueueID => $Param{QueueID},
Comment => "Because the old ticket [$Tn] is '$State{Name}'",
AutoResponseType => 'auto reply/new ticket',
LinkToTicketID => $TicketID,
);
if ( !$TicketID ) {
return;
}
@Return = ( 3, $TicketID );
}
# reject follow up
elsif ( !$BounceEmailAsFollowUp && $FollowUpPossible =~ /reject/i && $State{TypeName} =~ /^(removed|close)/i ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Info',
Key => 'Kernel::System::PostMaster',
Value => "Follow up for [$Tn] but follow up not possible. Follow up rejected.",
);
# send reject mail and add article to ticket
my $Run = $Self->{RejectObject}->Run(
TicketID => $TicketID,
InmailUserID => $Self->{PostmasterUserID},
GetParam => $GetParam,
Lock => $Lock,
Tn => $Tn,
Comment => 'Follow up rejected.',
AutoResponseType => 'auto reject',
);
if ( !$Run ) {
return;
}
@Return = ( 4, $TicketID );
}
# create normal follow up
else {
my $Run = $Self->{FollowUpObject}->Run(
TicketID => $TicketID,
InmailUserID => $Self->{PostmasterUserID},
GetParam => $GetParam,
Lock => $Lock,
Tn => $Tn,
AutoResponseType => 'auto follow up',
);
if ( !$Run ) {
return;
}
@Return = ( 2, $TicketID );
}
}
# create new ticket
else {
if ( $Param{Queue} && !$Param{QueueID} ) {
# queue lookup if queue name is given
$Param{QueueID} = $Kernel::OM->Get('Kernel::System::Queue')->QueueLookup(
Queue => $Param{Queue},
);
}
# get queue from From: or To:
if ( !$Param{QueueID} ) {
$Param{QueueID} = $Self->{DestQueueObject}->GetQueueID( Params => $GetParam );
}
# check if trusted returns a new queue id
my $TQueueID = $Self->{DestQueueObject}->GetTrustedQueueID(
Params => $GetParam,
);
if ($TQueueID) {
$Param{QueueID} = $TQueueID;
}
$TicketID = $Self->{NewTicketObject}->Run(
InmailUserID => $Self->{PostmasterUserID},
GetParam => $GetParam,
QueueID => $Param{QueueID},
AutoResponseType => 'auto reply',
);
return if !$TicketID;
@Return = ( 1, $TicketID );
}
# run all PostFilterModules (modify email params)
if ( ref $ConfigObject->Get('PostMaster::PostFilterModule') eq 'HASH' ) {
my %Jobs = %{ $ConfigObject->Get('PostMaster::PostFilterModule') };
# get main objects
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
JOB:
for my $Job ( sort keys %Jobs ) {
return if !$MainObject->Require( $Jobs{$Job}->{Module} );
my $FilterObject = $Jobs{$Job}->{Module}->new(
%{$Self},
);
if ( !$FilterObject ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Error',
Key => 'Kernel::System::PostMaster',
Value => "new() of PostFilterModule $Jobs{$Job}->{Module} not successfully!",
);
next JOB;
}
# modify params
my $Run = $FilterObject->Run(
TicketID => $TicketID,
GetParam => $GetParam,
JobConfig => $Jobs{$Job},
Return => $Return[0],
UserID => $Self->{PostmasterUserID},
);
if ( !$Run ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Error',
Key => 'Kernel::System::PostMaster',
Value => "Execute Run() of PostFilterModule $Jobs{$Job}->{Module} not successfully!",
);
}
}
}
return @Return;
}
=head2 CheckFollowUp()
to detect the ticket number in processing email
my ($TicketNumber, $TicketID) = $PostMasterObject->CheckFollowUp(
Subject => 'Re: [Ticket:#123456] Some Subject',
);
=cut
sub CheckFollowUp {
my ( $Self, %Param ) = @_;
# get ticket object
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
# get config objects
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# Load CheckFollowUp Modules
my $Jobs = $ConfigObject->Get('PostMaster::CheckFollowUpModule');
if ( IsHashRefWithData($Jobs) ) {
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
JOB:
for my $Job ( sort keys %$Jobs ) {
my $Module = $Jobs->{$Job};
return if !$MainObject->Require( $Jobs->{$Job}->{Module} );
my $CheckObject = $Jobs->{$Job}->{Module}->new(
%{$Self},
);
if ( !$CheckObject ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Error',
Key => 'Kernel::System::PostMaster',
Value => "new() of CheckFollowUp $Jobs->{$Job}->{Module} not successfully!",
);
next JOB;
}
my $TicketID = $CheckObject->Run(
%Param,
UserID => $Self->{PostmasterUserID},
);
if ($TicketID) {
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
DynamicFields => 0,
);
if (%Ticket) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Debug',
Key => 'Kernel::System::PostMaster',
Value =>
"Found follow up ticket with TicketNumber '$Ticket{TicketNumber}' and TicketID '$TicketID'.",
);
return ( $Ticket{TicketNumber}, $TicketID );
}
}
}
}
return;
}
=head2 GetEmailParams()
to get all configured PostmasterX-Header email headers
my %Header = $PostMasterObject->GetEmailParams();
=cut
sub GetEmailParams {
my ( $Self, %Param ) = @_;
my %GetParam;
# parse section
HEADER:
for my $Param ( @{ $Self->{'PostmasterX-Header'} } ) {
# do not scan x-otrs headers if mailbox is not marked as trusted
next HEADER if ( !$Self->{Trusted} && $Param =~ /^x-otrs/i );
$GetParam{$Param} = $Self->{ParserObject}->GetParam( WHAT => $Param );
next HEADER if !$GetParam{$Param};
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Debug',
Key => 'Kernel::System::PostMaster',
Value => "$Param: " . $GetParam{$Param},
);
}
# set compat. headers
if ( $GetParam{'Message-Id'} ) {
$GetParam{'Message-ID'} = $GetParam{'Message-Id'};
}
if ( $GetParam{'Reply-To'} ) {
$GetParam{'ReplyTo'} = $GetParam{'Reply-To'};
}
if (
$GetParam{'Mailing-List'}
|| $GetParam{'Precedence'}
|| $GetParam{'X-Loop'}
|| $GetParam{'X-No-Loop'}
|| $GetParam{'X-OTRS-Loop'}
|| (
$GetParam{'Auto-Submitted'}
&& substr( $GetParam{'Auto-Submitted'}, 0, 5 ) eq 'auto-'
)
)
{
$GetParam{'X-OTRS-Loop'} = 'yes';
}
if ( !$GetParam{'X-Sender'} ) {
# get sender email
my @EmailAddresses = $Self->{ParserObject}->SplitAddressLine(
Line => $GetParam{From},
);
for my $Email (@EmailAddresses) {
$GetParam{'X-Sender'} = $Self->{ParserObject}->GetEmailAddress(
Email => $Email,
);
}
}
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
# set sender type if not given
for my $Key (qw(X-OTRS-SenderType X-OTRS-FollowUp-SenderType)) {
if ( !$GetParam{$Key} ) {
$GetParam{$Key} = 'customer';
}
# check if X-OTRS-SenderType exists, if not, set customer
if ( !$ArticleObject->ArticleSenderTypeLookup( SenderType => $GetParam{$Key} ) ) {
$Self->{CommunicationLogObject}->ObjectLog(
ObjectLogType => 'Message',
Priority => 'Error',
Key => 'Kernel::System::PostMaster',
Value => "Can't find sender type '$GetParam{$Key}' in db, take 'customer'",
);
$GetParam{$Key} = 'customer';
}
}
# Set article customer visibility if not given.
for my $Key (qw(X-OTRS-IsVisibleForCustomer X-OTRS-FollowUp-IsVisibleForCustomer)) {
if ( !defined $GetParam{$Key} ) {
$GetParam{$Key} = 1;
}
}
# Get body.
$GetParam{Body} = $Self->{ParserObject}->GetMessageBody();
# Get content type, disposition and charset.
$GetParam{'Content-Type'} = $Self->{ParserObject}->GetReturnContentType();
$GetParam{'Content-Disposition'} = $Self->{ParserObject}->GetContentDisposition();
$GetParam{Charset} = $Self->{ParserObject}->GetReturnCharset();
# Get attachments.
my @Attachments = $Self->{ParserObject}->GetAttachments();
$GetParam{Attachment} = \@Attachments;
return \%GetParam;
}
1;
=head1 TERMS AND CONDITIONS
This software is part of the OTRS project (L).
This software comes with ABSOLUTELY NO WARRANTY. For details, see
the enclosed file COPYING for license information (GPL). If you
did not receive this file, see L.
=cut