1# --
2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
3# --
4# This software comes with ABSOLUTELY NO WARRANTY. For details, see
5# the enclosed file COPYING for license information (GPL). If you
6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
7# --
8
9package Kernel::System::Ticket::Article::Backend::Email;
10
11use strict;
12use warnings;
13
14use Mail::Address;
15
16use Kernel::System::VariableCheck qw(:all);
17
18use parent 'Kernel::System::Ticket::Article::Backend::MIMEBase';
19
20our @ObjectDependencies = (
21    'Kernel::Config',
22    'Kernel::System::CustomerUser',
23    'Kernel::System::DB',
24    'Kernel::System::Email',
25    'Kernel::System::HTMLUtils',
26    'Kernel::System::Log',
27    'Kernel::System::Main',
28    'Kernel::System::PostMaster::LoopProtection',
29    'Kernel::System::State',
30    'Kernel::System::TemplateGenerator',
31    'Kernel::System::Ticket',
32    'Kernel::System::Ticket::Article',
33    'Kernel::System::DateTime',
34    'Kernel::System::MailQueue',
35);
36
37=head1 NAME
38
39Kernel::System::Ticket::Article::Backend::Email - backend class for email based articles
40
41=head1 DESCRIPTION
42
43This class provides functions to manipulate email based articles in the database.
44
45Inherits from L<Kernel::System::Ticket::Article::Backend::MIMEBase>, please have a look there for its base API,
46and below for the additional functions this backend provides.
47
48=head1 PUBLIC INTERFACE
49
50=cut
51
52sub ChannelNameGet {
53    return 'Email';
54}
55
56=head2 ArticleGetByMessageID()
57
58Return article data by supplied message ID.
59
60    my %Article = $ArticleBackendObject->ArticleGetByMessageID(
61        MessageID     => '<13231231.1231231.32131231@example.com>',     # (required)
62        DynamicFields => 1,                                             # (optional) To include the dynamic field values for this article on the return structure.
63        RealNames     => 1,                                             # (optional) To include the From/To/Cc/Bcc fields with real names.
64    );
65
66=cut
67
68sub ArticleGetByMessageID {
69    my ( $Self, %Param ) = @_;
70
71    if ( !$Param{MessageID} ) {
72        $Kernel::OM->Get('Kernel::System::Log')->Log(
73            Priority => 'error',
74            Message  => 'Need MessageID!',
75        );
76        return;
77    }
78
79    my $MD5 = $Kernel::OM->Get('Kernel::System::Main')->MD5sum( String => $Param{MessageID} );
80
81    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
82
83    # Get ticket and article ID from meta article table.
84    return if !$DBObject->Prepare(
85        SQL => '
86            SELECT sa.id, sa.ticket_id FROM article sa
87            LEFT JOIN article_data_mime sadm ON sa.id = sadm.article_id
88            WHERE sadm.a_message_id_md5 = ?
89        ',
90        Bind  => [ \$MD5 ],
91        Limit => 10,
92    );
93
94    my $Count = 0;
95    while ( my @Row = $DBObject->FetchrowArray() ) {
96        $Param{ArticleID} = $Row[0];
97        $Param{TicketID}  = $Row[1];
98        $Count++;
99    }
100
101    # No reference found.
102    return if $Count == 0;
103    return if !$Param{TicketID} || !$Param{ArticleID};
104
105    # More than one reference found! That should not happen, since 'a message_id' should be unique!
106    if ( $Count > 1 ) {
107        $Kernel::OM->Get('Kernel::System::Log')->Log(
108            Priority => 'notice',
109            Message =>
110                "The MessageID '$Param{MessageID}' is in your database more than one time! That should not happen, since 'a message_id' should be unique!",
111        );
112        return;
113    }
114
115    return $Self->ArticleGet(
116        %Param,
117    );
118}
119
120=head2 ArticleSend()
121
122Send article via email and create article with attachments.
123
124    my $ArticleID = $ArticleBackendObject->ArticleSend(
125        TicketID             => 123,                              # (required)
126        SenderTypeID         => 1,                                # (required)
127                                                                  # or
128        SenderType           => 'agent',                          # (required) agent|system|customer
129        IsVisibleForCustomer => 1,                                # (required) Is article visible for customer?
130        UserID               => 123,                              # (required)
131
132        From        => 'Some Agent <email@example.com>',                       # required
133        To          => 'Some Customer A <customer-a@example.com>',             # required if both Cc and Bcc are not present
134        Cc          => 'Some Customer B <customer-b@example.com>',             # required if both To and Bcc are not present
135        Bcc         => 'Some Customer C <customer-c@example.com>',             # required if both To and Cc are not present
136        ReplyTo     => 'Some Customer B <customer-b@example.com>',             # not required, is possible to use 'Reply-To' instead
137        Subject     => 'some short description',                               # required
138        Body        => 'the message text',                                     # required
139        InReplyTo   => '<asdasdasd.12@example.com>',                           # not required but useful
140        References  => '<asdasdasd.1@example.com> <asdasdasd.12@example.com>', # not required but useful
141        Charset     => 'iso-8859-15'
142        MimeType    => 'text/plain',
143        Loop        => 0, # 1|0 used for bulk emails
144        Attachment => [
145            {
146                Content     => $Content,
147                ContentType => $ContentType,
148                Filename    => 'lala.txt',
149            },
150            {
151                Content     => $Content,
152                ContentType => $ContentType,
153                Filename    => 'lala1.txt',
154            },
155        ],
156        EmailSecurity => {
157            Backend     => 'PGP',                       # PGP or SMIME
158            Method      => 'Detached',                  # Optional Detached or Inline (defaults to Detached)
159            SignKey     => '81877F5E',                  # Optional
160            EncryptKeys => [ '81877F5E', '3b630c80' ],  # Optional
161        }
162        HistoryType    => 'OwnerUpdate',  # Move|AddNote|PriorityUpdate|WebRequestCustomer|...
163        HistoryComment => 'Some free text!',
164        NoAgentNotify  => 0,            # if you don't want to send agent notifications
165    );
166
167
168    my $ArticleID = $ArticleBackendObject->ArticleSend(                (Backwards compatibility)
169        TicketID             => 123,                              # (required)
170        SenderTypeID         => 1,                                # (required)
171                                                                  # or
172        SenderType           => 'agent',                          # (required) agent|system|customer
173        IsVisibleForCustomer => 1,                                # (required) Is article visible for customer?
174        UserID               => 123,                              # (required)
175
176        From        => 'Some Agent <email@example.com>',                       # required
177        To          => 'Some Customer A <customer-a@example.com>',             # required if both Cc and Bcc are not present
178        Cc          => 'Some Customer B <customer-b@example.com>',             # required if both To and Bcc are not present
179        Bcc         => 'Some Customer C <customer-c@example.com>',             # required if both To and Cc are not present
180        ReplyTo     => 'Some Customer B <customer-b@example.com>',             # not required, is possible to use 'Reply-To' instead
181        Subject     => 'some short description',                               # required
182        Body        => 'the message text',                                     # required
183        InReplyTo   => '<asdasdasd.12@example.com>',                           # not required but useful
184        References  => '<asdasdasd.1@example.com> <asdasdasd.12@example.com>', # not required but useful
185        Charset     => 'iso-8859-15'
186        MimeType    => 'text/plain',
187        Loop        => 0, # 1|0 used for bulk emails
188        Attachment => [
189            {
190                Content     => $Content,
191                ContentType => $ContentType,
192                Filename    => 'lala.txt',
193            },
194            {
195                Content     => $Content,
196                ContentType => $ContentType,
197                Filename    => 'lala1.txt',
198            },
199        ],
200        Sign => {
201            Type    => 'PGP',
202            SubType => 'Inline|Detached',
203            Key     => '81877F5E',
204            Type    => 'SMIME',
205            Key     => '3b630c80',
206        },
207        Crypt => {
208            Type    => 'PGP',
209            SubType => 'Inline|Detached',
210            Key     => '81877F5E',
211            Type    => 'SMIME',
212            Key     => '3b630c80',
213        },
214        HistoryType    => 'OwnerUpdate',  # Move|AddNote|PriorityUpdate|WebRequestCustomer|...
215        HistoryComment => 'Some free text!',
216        NoAgentNotify  => 0,            # if you don't want to send agent notifications
217    );
218
219Events:
220    ArticleSend
221
222=cut
223
224sub ArticleSend {
225    my ( $Self, %Param ) = @_;
226
227    my $ToOrig      = $Param{To}          || '';
228    my $Loop        = $Param{Loop}        || 0;
229    my $HistoryType = $Param{HistoryType} || 'SendAnswer';
230
231    my $ArticleObject  = $Kernel::OM->Get('Kernel::System::Ticket::Article');
232    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
233
234    # Lookup if no ID is passed.
235    if ( $Param{SenderType} && !$Param{SenderTypeID} ) {
236        $Param{SenderTypeID} = $ArticleObject->ArticleSenderTypeLookup( SenderType => $Param{SenderType} );
237    }
238
239    for my $Needed (qw(TicketID UserID SenderTypeID From Body Charset MimeType)) {
240        if ( !$Param{$Needed} ) {
241            $Kernel::OM->Get('Kernel::System::Log')->Log(
242                Priority => 'error',
243                Message  => "Need $Needed!",
244            );
245            return;
246        }
247    }
248
249    if ( !defined $Param{IsVisibleForCustomer} ) {
250        $Kernel::OM->Get('Kernel::System::Log')->Log(
251            Priority => 'error',
252            Message  => "Need IsVisibleForCustomer!",
253        );
254        return;
255    }
256
257    # Map ReplyTo into Reply-To if present.
258    if ( $Param{ReplyTo} ) {
259        $Param{'Reply-To'} = $Param{ReplyTo};
260    }
261
262    # Clean up body string.
263    $Param{Body} =~ s/(\r\n|\n\r)/\n/g;
264    $Param{Body} =~ s/\r/\n/g;
265
266    # initialize parameter for attachments, so that the content pushed into that ref from
267    # EmbeddedImagesExtract will stay available
268    if ( !$Param{Attachment} ) {
269        $Param{Attachment} = [];
270    }
271
272    # check for base64 images in body and process them
273    $Kernel::OM->Get('Kernel::System::HTMLUtils')->EmbeddedImagesExtract(
274        DocumentRef    => \$Param{Body},
275        AttachmentsRef => $Param{Attachment},
276    );
277
278    # create article
279    my $Time      = $DateTimeObject->ToEpoch();
280    my $Random    = rand 999999;
281    my $FQDN      = $Kernel::OM->Get('Kernel::Config')->Get('FQDN');
282    my $MessageID = "<$Time.$Random\@$FQDN>";
283    my $ArticleID = $Self->ArticleCreate(
284        %Param,
285        MessageID => $MessageID,
286    );
287    return if !$ArticleID;
288
289    # Send the mail
290    my $Result = $Kernel::OM->Get('Kernel::System::Email')->Send(
291        %Param,
292        ArticleID    => $ArticleID,
293        'Message-ID' => $MessageID,
294    );
295
296    # return if mail wasn't sent
297    if ( !$Result->{Success} ) {
298        $Kernel::OM->Get('Kernel::System::Log')->Log(
299            Message  => "Impossible to send message to: $Param{'To'} .",
300            Priority => 'error',
301        );
302        return;
303    }
304
305    # write article to file system
306    my $Plain = $Self->ArticleWritePlain(
307        ArticleID => $ArticleID,
308        Email     => sprintf( "%s\n%s", $Result->{Data}->{Header}, $Result->{Data}->{Body} ),
309        UserID    => $Param{UserID},
310    );
311    return if !$Plain;
312
313    # log
314    $Kernel::OM->Get('Kernel::System::Log')->Log(
315        Priority => 'info',
316        Message  => sprintf(
317            "Queued email to '%s' from '%s'. HistoryType => %s, Subject => %s;",
318            $Param{To},
319            $Param{From},
320            $HistoryType,
321            $Param{Subject},
322        ),
323    );
324
325    # event
326    $Self->EventHandler(
327        Event => 'ArticleSend',
328        Data  => {
329            TicketID  => $Param{TicketID},
330            ArticleID => $ArticleID,
331        },
332        UserID => $Param{UserID},
333    );
334
335    return $ArticleID;
336}
337
338=head2 ArticleBounce()
339
340Bounce an article.
341
342    my $Success = $ArticleBackendObject->ArticleBounce(
343        From      => 'some@example.com',
344        To        => 'webmaster@example.com',
345        TicketID  => 123,
346        ArticleID => 123,
347        UserID    => 123,
348    );
349
350Events:
351    ArticleBounce
352
353=cut
354
355sub ArticleBounce {
356    my ( $Self, %Param ) = @_;
357
358    for my $Item (qw(TicketID ArticleID From To UserID)) {
359        if ( !$Param{$Item} ) {
360            $Kernel::OM->Get('Kernel::System::Log')->Log(
361                Priority => 'error',
362                Message  => "Need $Item!",
363            );
364            return;
365        }
366    }
367
368    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
369
370    # create message id
371    my $Time         = $DateTimeObject->ToEpoch();
372    my $Random       = rand 999999;
373    my $FQDN         = $Kernel::OM->Get('Kernel::Config')->Get('FQDN');
374    my $NewMessageID = "<$Time.$Random.0\@$FQDN>";
375    my $Email        = $Self->ArticlePlain( ArticleID => $Param{ArticleID} );
376
377    # check if plain email exists
378    if ( !$Email ) {
379        $Kernel::OM->Get('Kernel::System::Log')->Log(
380            Priority => 'error',
381            Message  => "No such plain article for ArticleID ($Param{ArticleID})!",
382        );
383        return;
384    }
385
386    # pipe all into sendmail
387    my $BounceSent = $Kernel::OM->Get('Kernel::System::Email')->Bounce(
388        'Message-ID' => $NewMessageID,
389        From         => $Param{From},
390        To           => $Param{To},
391        Email        => $Email,
392    );
393
394    return if !$BounceSent->{Success};
395
396    # write history
397    my $HistoryType = $Param{HistoryType} || 'Bounce';
398    $Kernel::OM->Get('Kernel::System::Ticket')->HistoryAdd(
399        TicketID     => $Param{TicketID},
400        ArticleID    => $Param{ArticleID},
401        HistoryType  => $HistoryType,
402        Name         => "\%\%$Param{To}",
403        CreateUserID => $Param{UserID},
404    );
405
406    # event
407    $Self->EventHandler(
408        Event => 'ArticleBounce',
409        Data  => {
410            TicketID  => $Param{TicketID},
411            ArticleID => $Param{ArticleID},
412        },
413        UserID => $Param{UserID},
414    );
415
416    return 1;
417}
418
419=head2 SendAutoResponse()
420
421Send an auto response to a customer via email.
422
423    my $ArticleID = $ArticleBackendObject->SendAutoResponse(
424        TicketID         => 123,
425        AutoResponseType => 'auto reply',
426        OrigHeader       => {
427            From    => 'some@example.com',
428            Subject => 'For the message!',
429        },
430        UserID => 123,
431    );
432
433Events:
434    ArticleAutoResponse
435
436=cut
437
438sub SendAutoResponse {
439    my ( $Self, %Param ) = @_;
440
441    # check needed stuff
442    for my $Item (qw(TicketID UserID OrigHeader AutoResponseType)) {
443        if ( !$Param{$Item} ) {
444            $Kernel::OM->Get('Kernel::System::Log')->Log(
445                Priority => 'error',
446                Message  => "Need $Item!",
447            );
448            return;
449        }
450    }
451
452    # return if no notification is active
453    return 1 if $Self->{SendNoNotification};
454
455    # get orig email header
456    my %OrigHeader = %{ $Param{OrigHeader} };
457
458    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
459
460    # get ticket
461    my %Ticket = $TicketObject->TicketGet(
462        TicketID      => $Param{TicketID},
463        DynamicFields => 0,                  # not needed here, TemplateGenerator will fetch the ticket on its own
464    );
465
466    # get auto default responses
467    my %AutoResponse = $Kernel::OM->Get('Kernel::System::TemplateGenerator')->AutoResponse(
468        TicketID         => $Param{TicketID},
469        AutoResponseType => $Param{AutoResponseType},
470        OrigHeader       => $Param{OrigHeader},
471        UserID           => $Param{UserID},
472    );
473
474    # return if no valid auto response exists
475    return if !$AutoResponse{Text};
476    return if !$AutoResponse{SenderRealname};
477    return if !$AutoResponse{SenderAddress};
478
479    # send if notification should be sent (not for closed tickets)!?
480    my %State = $Kernel::OM->Get('Kernel::System::State')->StateGet( ID => $Ticket{StateID} );
481    if (
482        $Param{AutoResponseType} eq 'auto reply'
483        && ( $State{TypeName} eq 'closed' || $State{TypeName} eq 'removed' )
484        )
485    {
486
487        # add history row
488        $TicketObject->HistoryAdd(
489            TicketID    => $Param{TicketID},
490            HistoryType => 'Misc',
491            Name        => "Sent no auto response or agent notification because ticket is "
492                . "state-type '$State{TypeName}'!",
493            CreateUserID => $Param{UserID},
494        );
495
496        # return
497        return;
498    }
499
500    # log that no auto response was sent!
501    if ( $OrigHeader{'X-OTRS-Loop'} && $OrigHeader{'X-OTRS-Loop'} !~ /^(false|no)$/i ) {
502
503        # add history row
504        $TicketObject->HistoryAdd(
505            TicketID    => $Param{TicketID},
506            HistoryType => 'Misc',
507            Name        => "Sent no auto-response because the sender doesn't want "
508                . "an auto-response (e. g. loop or precedence header)",
509            CreateUserID => $Param{UserID},
510        );
511        $Kernel::OM->Get('Kernel::System::Log')->Log(
512            Priority => 'info',
513            Message  => "Sent no '$Param{AutoResponseType}' for Ticket ["
514                . "$Ticket{TicketNumber}] ($OrigHeader{From}) because the "
515                . "sender doesn't want an auto-response (e. g. loop or precedence header)"
516        );
517        return;
518    }
519
520    # check reply to for auto response recipient
521    if ( $OrigHeader{ReplyTo} ) {
522        $OrigHeader{From} = $OrigHeader{ReplyTo};
523    }
524
525    # get loop protection object
526    my $LoopProtectionObject = $Kernel::OM->Get('Kernel::System::PostMaster::LoopProtection');
527
528    # create email parser object
529    my $EmailParser = Kernel::System::EmailParser->new(
530        Mode => 'Standalone',
531    );
532
533    my @AutoReplyAddresses;
534    my @Addresses = $EmailParser->SplitAddressLine( Line => $OrigHeader{From} );
535    ADDRESS:
536    for my $Address (@Addresses) {
537        my $Email = $EmailParser->GetEmailAddress( Email => $Address );
538        if ( !$Email ) {
539
540            # add it to ticket history
541            $TicketObject->HistoryAdd(
542                TicketID     => $Param{TicketID},
543                CreateUserID => $Param{UserID},
544                HistoryType  => 'Misc',
545                Name         => "Sent no auto response to '$Address' - no valid email address.",
546            );
547
548            # log
549            $Kernel::OM->Get('Kernel::System::Log')->Log(
550                Priority => 'notice',
551                Message  => "Sent no auto response to '$Address' because of invalid address.",
552            );
553            next ADDRESS;
554
555        }
556        if ( !$LoopProtectionObject->Check( To => $Email ) ) {
557
558            # add history row
559            $TicketObject->HistoryAdd(
560                TicketID     => $Param{TicketID},
561                HistoryType  => 'LoopProtection',
562                Name         => "\%\%$Email",
563                CreateUserID => $Param{UserID},
564            );
565
566            # log
567            $Kernel::OM->Get('Kernel::System::Log')->Log(
568                Priority => 'notice',
569                Message  => "Sent no '$Param{AutoResponseType}' for Ticket ["
570                    . "$Ticket{TicketNumber}] ($Email) because of loop protection."
571            );
572            next ADDRESS;
573        }
574        else {
575
576            # increase loop count
577            return if !$LoopProtectionObject->SendEmail( To => $Email );
578        }
579
580        # check if sender is e. g. MAILER-DAEMON or Postmaster
581        my $NoAutoRegExp = $Kernel::OM->Get('Kernel::Config')->Get('SendNoAutoResponseRegExp');
582        if ( $Email =~ /$NoAutoRegExp/i ) {
583
584            # add it to ticket history
585            $TicketObject->HistoryAdd(
586                TicketID     => $Param{TicketID},
587                CreateUserID => $Param{UserID},
588                HistoryType  => 'Misc',
589                Name         => "Sent no auto response to '$Email', SendNoAutoResponseRegExp matched.",
590            );
591
592            # log
593            $Kernel::OM->Get('Kernel::System::Log')->Log(
594                Priority => 'info',
595                Message  => "Sent no auto response to '$Email' because config"
596                    . " option SendNoAutoResponseRegExp (/$NoAutoRegExp/i) matched.",
597            );
598            next ADDRESS;
599        }
600
601        push @AutoReplyAddresses, $Address;
602    }
603
604    my $AutoReplyAddresses = join( ', ', @AutoReplyAddresses );
605    my $Cc;
606
607    # also send CC to customer user if customer user id is used and addresses do not match
608    if ( $Ticket{CustomerUserID} ) {
609
610        my %CustomerUser = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerUserDataGet(
611            User => $Ticket{CustomerUserID},
612        );
613
614        if (
615            $CustomerUser{UserEmail}
616            && $OrigHeader{From} !~ /\Q$CustomerUser{UserEmail}\E/i
617            && $Param{IsVisibleForCustomer}
618            )
619        {
620            $Cc = $CustomerUser{UserEmail};
621        }
622    }
623
624    # get history type
625    my $HistoryType;
626    if ( $Param{AutoResponseType} =~ /^auto follow up$/i ) {
627        $HistoryType = 'SendAutoFollowUp';
628    }
629    elsif ( $Param{AutoResponseType} =~ /^auto reply$/i ) {
630        $HistoryType = 'SendAutoReply';
631    }
632    elsif ( $Param{AutoResponseType} =~ /^auto reply\/new ticket$/i ) {
633        $HistoryType = 'SendAutoReply';
634    }
635    elsif ( $Param{AutoResponseType} =~ /^auto reject$/i ) {
636        $HistoryType = 'SendAutoReject';
637    }
638    else {
639        $HistoryType = 'Misc';
640    }
641
642    if ( !@AutoReplyAddresses && !$Cc ) {
643        $Kernel::OM->Get('Kernel::System::Log')->Log(
644            Priority => 'info',
645            Message  => "No auto response addresses for Ticket [$Ticket{TicketNumber}]"
646                . " (TicketID=$Param{TicketID})."
647        );
648        return;
649    }
650
651    # Format sender realname and address because it maybe contains comma or other special symbols (see bug#13130).
652    my $From = Mail::Address->new( $AutoResponse{SenderRealname} // '', $AutoResponse{SenderAddress} );
653
654    # send email
655    my $ArticleID = $Self->ArticleSend(
656        IsVisibleForCustomer => 1,
657        SenderType           => 'system',
658        TicketID             => $Param{TicketID},
659        HistoryType          => $HistoryType,
660        HistoryComment       => "\%\%$AutoReplyAddresses",
661        From                 => $From->format(),
662        To                   => $AutoReplyAddresses,
663        Cc                   => $Cc,
664        Charset              => 'utf-8',
665        MimeType             => $AutoResponse{ContentType},
666        Subject              => $AutoResponse{Subject},
667        Body                 => $AutoResponse{Text},
668        InReplyTo            => $OrigHeader{'Message-ID'},
669        Loop                 => 1,
670        UserID               => $Param{UserID},
671    );
672
673    # log
674    $Kernel::OM->Get('Kernel::System::Log')->Log(
675        Priority => 'info',
676        Message  => "Sent auto response ($HistoryType) for Ticket [$Ticket{TicketNumber}]"
677            . " (TicketID=$Param{TicketID}, ArticleID=$ArticleID) to '$AutoReplyAddresses'."
678    );
679
680    # event
681    $Self->EventHandler(
682        Event => 'ArticleAutoResponse',
683        Data  => {
684            TicketID => $Param{TicketID},
685        },
686        UserID => $Param{UserID},
687    );
688
689    return 1;
690}
691
692=head2 ArticleTransmissionStatus()
693
694Get the transmission status for one article.
695
696    my $TransmissionStatus = $ArticleBackendObject->ArticleTransmissionStatus(
697        ArticleID => 123,   # required
698    );
699
700This returns something like:
701
702    $TransmissionStatus = {
703        ArticleID  => 123,
704        MessageID  => 456,
705        Message    => 'Descriptive message of last communication',  # only in case of failed status
706        CreateTime => '2017-01-01 12:34:56',
707        Status     => [Processing|Failed],
708        Attempts   => 1,                                            # only in case of processing status
709        DueTime    => '2017-01-02 12:34:56',                        # only in case of processing status
710    }
711
712=cut
713
714sub ArticleTransmissionStatus {
715    my ( $Self, %Param ) = @_;
716
717    if ( !$Param{ArticleID} ) {
718        $Kernel::OM->Get('Kernel::System::Log')->Log(
719            Priority => 'error',
720            Message  => 'Need ArticleID',
721        );
722        return;
723    }
724
725    my $Result = $Self->ArticleGetTransmissionError( %Param, );
726    return $Result if $Result && %{$Result};
727
728    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
729
730    return if !$DBObject->Prepare(
731        SQL =>
732            'SELECT article_id, create_time, attempts, due_time FROM mail_queue WHERE article_id = ?',
733        Bind => [ \$Param{ArticleID} ],
734    );
735
736    if ( my @Row = $DBObject->FetchrowArray() ) {
737        return {
738            ArticleID  => $Row[0],
739            CreateTime => $Row[1],
740            Attempts   => $Row[2],
741            DueTime    => $Row[3],
742            Status     => 'Processing',
743        };
744    }
745
746    return;
747}
748
749=head2 ArticleCreateTransmissionError()
750
751Creates a Transmission Error entry for one article.
752
753    my $Success = $ArticleBackendObject->ArticleCreateTransmissionError(
754        ArticleID => 123,                   # Required
755        MessageID => 456,                   # Optional
756        Message   => '',                    # Optional
757    );
758
759=cut
760
761sub ArticleCreateTransmissionError {
762    my ( $Self, %Param ) = @_;
763
764    # check needed stuff
765    for my $Field (qw{ArticleID}) {
766        if ( !$Param{$Field} ) {
767            $Kernel::OM->Get('Kernel::System::Log')->Log(
768                Priority => 'error',
769                Message  => "Need ${Field}!"
770            );
771            return;
772        }
773    }
774
775    my $SQL = 'INSERT INTO article_data_mime_send_error(';
776
777    my @Fields;
778    my @Bind;
779
780    my %MapDB = (
781        ArticleID => 'article_id',
782        MessageID => 'message_id',
783        Message   => 'log_message',
784    );
785
786    my @PlaceHolder;
787
788    for my $Field ( sort keys %MapDB ) {
789        if ( IsStringWithData( $Param{$Field} ) ) {
790            push @Fields,      $MapDB{$Field};
791            push @PlaceHolder, '?';
792            push @Bind,        \$Param{$Field};
793        }
794    }
795    push @Fields, 'create_time';
796
797    $SQL .= join( ', ', @Fields )
798        . ') values(';
799
800    $SQL .= join ', ', @PlaceHolder;
801
802    $SQL .= ', current_timestamp)';
803
804    # get database object
805    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
806
807    return if !$DBObject->Do(
808        SQL  => $SQL,
809        Bind => \@Bind,
810    );
811
812    return 1;
813}
814
815=head2 ArticleGetTransmissionError()
816
817Get the Transmission Error entry for a given article.
818
819    my %TransmissionError = $ArticleBackendObject->ArticleGetTransmissionError(
820        ArticleID => 123,   # Required
821    );
822
823    Returns:
824    {
825        ArticleID  => 123,
826        MessageID  => 456,
827        Message    => 'Descriptive message of last communication',
828        CreateTime => '2017-01-01 01:02:03',
829        Status     => 'Failed',
830    }
831    or undef in case of failure to retrive a record from the database.
832
833=cut
834
835sub ArticleGetTransmissionError {
836    my ( $Self, %Param ) = @_;
837
838    # check needed stuff
839    if ( !$Param{ArticleID} ) {
840        $Kernel::OM->Get('Kernel::System::Log')->Log(
841            Priority => 'error',
842            Message  => "Need ArticleID!"
843        );
844        return;
845    }
846
847    # prepare/filter ArticleID
848    $Param{ArticleID} = quotemeta( $Param{ArticleID} );
849    $Param{ArticleID} =~ s/\0//g;
850
851    # get database object
852    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
853
854    # can't open article, try database
855    return if !$DBObject->Prepare(
856        SQL =>
857            'SELECT article_id, message_id, log_message, create_time FROM article_data_mime_send_error WHERE article_id = ?',
858        Bind => [ \$Param{ArticleID} ],
859    );
860
861    my @Row = $DBObject->FetchrowArray();
862    if (@Row) {
863        return {
864            'ArticleID'  => $Row[0],
865            'MessageID'  => $Row[1],
866            'Message'    => $Row[2],
867            'CreateTime' => $Row[3],
868            'Status'     => 'Failed',
869        };
870    }
871
872    return;
873}
874
875=head2 ArticleUpdateTransmissionError()
876
877Updates the Transmission Error.
878
879    my $Result = $ArticleBackendObject->ArticleUpdateTransmissionError(
880        ArticleID => 123,                           # Required
881        MessageID => 456,                           # Optional
882        Message   => 'Short descriptive message',   # Optional
883    );
884
885Returns 1 on Success, undef on failure.
886
887=cut
888
889sub ArticleUpdateTransmissionError {
890    my ( $Self, %Param ) = @_;
891
892    # check needed stuff
893    if ( !$Param{ArticleID} ) {
894        $Kernel::OM->Get('Kernel::System::Log')->Log(
895            Priority => 'error',
896            Message  => "Need ArticleID!"
897        );
898        return;
899    }
900
901    my @FieldsToUpdate;
902    my @Bind;
903
904    if ( IsStringWithData( $Param{MessageID} ) ) {
905        push @FieldsToUpdate, 'message_id = ?';
906        push @Bind,           \$Param{MessageID};
907    }
908
909    if ( IsStringWithData( $Param{Message} ) ) {
910        push @FieldsToUpdate, 'log_message = ?';
911        push @Bind,           \$Param{Message};
912    }
913
914    return if !scalar @Bind;
915
916    my $SQL = 'UPDATE article_data_mime_send_error SET '
917        . join( ', ', @FieldsToUpdate )
918        . ' WHERE article_id = ?';
919
920    push @Bind, \$Param{ArticleID};
921
922    # get database object
923    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
924
925    # db update
926    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
927        SQL  => $SQL,
928        Bind => \@Bind,
929    );
930
931    return 1;
932}
933
9341;
935
936=head1 TERMS AND CONDITIONS
937
938This software is part of the OTRS project (L<https://otrs.org/>).
939
940This software comes with ABSOLUTELY NO WARRANTY. For details, see
941the enclosed file COPYING for license information (GPL). If you
942did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
943
944=cut
945