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