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::Modules::AgentTicketEmailOutbound; 10 11use strict; 12use warnings; 13 14use Kernel::System::VariableCheck qw(:all); 15use Kernel::Language qw(Translatable); 16use Mail::Address; 17 18our $ObjectManagerDisabled = 1; 19 20sub new { 21 my ( $Type, %Param ) = @_; 22 23 # allocate new hash for object 24 my $Self = {%Param}; 25 bless( $Self, $Type ); 26 27 # Try to load draft if requested. 28 if ( 29 $Kernel::OM->Get('Kernel::Config')->Get("Ticket::Frontend::$Self->{Action}")->{FormDraft} 30 && $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'LoadFormDraft' ) 31 && $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'FormDraftID' ) 32 ) 33 { 34 $Self->{LoadedFormDraftID} = $Kernel::OM->Get('Kernel::System::Web::Request')->LoadFormDraft( 35 FormDraftID => $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'FormDraftID' ), 36 UserID => $Self->{UserID}, 37 ); 38 } 39 40 $Self->{Debug} = $Param{Debug} || 0; 41 42 return $Self; 43} 44 45sub Run { 46 my ( $Self, %Param ) = @_; 47 48 my $Output; 49 50 # get ticket object 51 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 52 53 # get ACL restrictions 54 $TicketObject->TicketAcl( 55 Data => '-', 56 TicketID => $Self->{TicketID}, 57 ReturnType => 'Action', 58 ReturnSubType => '-', 59 UserID => $Self->{UserID}, 60 ); 61 my %AclAction = $TicketObject->TicketAclActionData(); 62 63 # get layout object 64 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 65 66 # check if ACL restrictions exist 67 if ( IsHashRefWithData( \%AclAction ) ) { 68 69 # show error screen if ACL prohibits this action 70 if ( defined $AclAction{ $Self->{Action} } && $AclAction{ $Self->{Action} } eq '0' ) { 71 72 return $LayoutObject->NoPermission( WithHeader => 'yes' ); 73 } 74 } 75 76 # Check for failed draft loading request. 77 if ( 78 $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'LoadFormDraft' ) 79 && !$Self->{LoadedFormDraftID} 80 ) 81 { 82 return $LayoutObject->ErrorScreen( 83 Message => Translatable('Loading draft failed!'), 84 Comment => Translatable('Please contact the administrator.'), 85 ); 86 } 87 88 if ( $Self->{Subaction} eq 'SendEmail' ) { 89 90 # challenge token check for write action 91 $LayoutObject->ChallengeTokenCheck(); 92 93 $Output = $Self->SendEmail(); 94 } 95 elsif ( $Self->{Subaction} eq 'AJAXUpdateTemplate' ) { 96 97 my %GetParam; 98 for my $Key ( 99 qw( 100 NewStateID NewPriorityID TimeUnits IsVisibleForCustomer Title Body Subject NewQueueID 101 Year Month Day Hour Minute NewOwnerID NewOwnerType OldOwnerID NewResponsibleID 102 TypeID ServiceID SLAID Expand ReplyToArticle StandardTemplateID CreateArticle 103 FormID ElementChanged 104 ) 105 ) 106 { 107 $GetParam{$Key} = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => $Key ); 108 } 109 110 my %Ticket = $TicketObject->TicketGet( TicketID => $Self->{TicketID} ); 111 my $CustomerUser = $Ticket{CustomerUserID}; 112 my $QueueID = $Ticket{QueueID}; 113 114 # get dynamic field values form http request 115 my %DynamicFieldValues; 116 117 # convert dynamic field values into a structure for ACLs 118 my %DynamicFieldACLParameters; 119 DYNAMICFIELD: 120 for my $DynamicFieldItem ( sort keys %DynamicFieldValues ) { 121 next DYNAMICFIELD if !$DynamicFieldItem; 122 next DYNAMICFIELD if !defined $DynamicFieldValues{$DynamicFieldItem}; 123 124 $DynamicFieldACLParameters{ 'DynamicField_' . $DynamicFieldItem } = $DynamicFieldValues{$DynamicFieldItem}; 125 } 126 127 # get config object 128 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 129 130 # get list type 131 my $TreeView = 0; 132 if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) { 133 $TreeView = 1; 134 } 135 136 my $NextStates = $Self->_GetNextStates( 137 %GetParam, 138 CustomerUserID => $CustomerUser || '', 139 QueueID => $QueueID, 140 ); 141 142 # update Dynamic Fields Possible Values via AJAX 143 my @DynamicFieldAJAX; 144 145 # get config for frontend module 146 my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}"); 147 148 # get the dynamic fields for this screen 149 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 150 Valid => 1, 151 ObjectType => [ 'Ticket', 'Article' ], 152 FieldFilter => $Config->{DynamicField} || {}, 153 ); 154 155 # get dynamic field backend object 156 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 157 158 # cycle trough the activated Dynamic Fields for this screen 159 DYNAMICFIELD: 160 for my $DynamicFieldConfig ( @{$DynamicField} ) { 161 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 162 163 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 164 DynamicFieldConfig => $DynamicFieldConfig, 165 Behavior => 'IsACLReducible', 166 ); 167 next DYNAMICFIELD if !$IsACLReducible; 168 169 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 170 DynamicFieldConfig => $DynamicFieldConfig, 171 ); 172 173 # convert possible values key => value to key => key for ACLs using a Hash slice 174 my %AclData = %{$PossibleValues}; 175 @AclData{ keys %AclData } = keys %AclData; 176 177 # set possible values filter from ACLs 178 my $ACL = $TicketObject->TicketAcl( 179 %GetParam, 180 Action => $Self->{Action}, 181 TicketID => $Self->{TicketID}, 182 QueueID => $QueueID, 183 ReturnType => 'Ticket', 184 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 185 Data => \%AclData, 186 UserID => $Self->{UserID}, 187 ); 188 if ($ACL) { 189 my %Filter = $TicketObject->TicketAclData(); 190 191 # convert Filer key => key back to key => value using map 192 %{$PossibleValues} = map { $_ => $PossibleValues->{$_} } keys %Filter; 193 } 194 195 my $DataValues = $DynamicFieldBackendObject->BuildSelectionDataGet( 196 DynamicFieldConfig => $DynamicFieldConfig, 197 PossibleValues => $PossibleValues, 198 Value => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 199 ) || $PossibleValues; 200 201 # add dynamic field to the list of fields to update 202 push( 203 @DynamicFieldAJAX, 204 { 205 Name => 'DynamicField_' . $DynamicFieldConfig->{Name}, 206 Data => $DataValues, 207 SelectedID => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 208 Translation => $DynamicFieldConfig->{Config}->{TranslatableValues} || 0, 209 Max => 100, 210 } 211 ); 212 } 213 214 my $StandardTemplates = $Self->_GetStandardTemplates( 215 %GetParam, 216 QueueID => $QueueID || '', 217 ); 218 219 my @TemplateAJAX; 220 221 # get upload cache object 222 my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache'); 223 224 # update ticket body and attachments if needed. 225 if ( $GetParam{ElementChanged} eq 'StandardTemplateID' ) { 226 my @TicketAttachments; 227 my $TemplateText; 228 229 # remove all attachments from the Upload cache 230 my $RemoveSuccess = $UploadCacheObject->FormIDRemove( 231 FormID => $GetParam{FormID}, 232 ); 233 if ( !$RemoveSuccess ) { 234 $Kernel::OM->Get('Kernel::System::Log')->Log( 235 Priority => 'error', 236 Message => "Form attachments could not be deleted!", 237 ); 238 } 239 240 # get the template text and set new attachments if a template is selected 241 my $TemplateGenerator = $Kernel::OM->Get('Kernel::System::TemplateGenerator'); 242 243 if ( IsPositiveInteger( $GetParam{StandardTemplateID} ) ) { 244 245 # set template text, replace smart tags (limited as ticket is not created) 246 $TemplateText = $TemplateGenerator->Template( 247 TemplateID => $GetParam{StandardTemplateID}, 248 TicketID => $Self->{TicketID}, 249 UserID => $Self->{UserID}, 250 ); 251 252 # create StdAttachmentObject 253 my $StdAttachmentObject = $Kernel::OM->Get('Kernel::System::StdAttachment'); 254 255 # add std. attachments to ticket 256 my %AllStdAttachments = $StdAttachmentObject->StdAttachmentStandardTemplateMemberList( 257 StandardTemplateID => $GetParam{StandardTemplateID}, 258 ); 259 for my $ID ( sort keys %AllStdAttachments ) { 260 my %AttachmentsData = $StdAttachmentObject->StdAttachmentGet( ID => $ID ); 261 $UploadCacheObject->FormIDAddFile( 262 FormID => $GetParam{FormID}, 263 Disposition => 'attachment', 264 %AttachmentsData, 265 ); 266 } 267 268 # send a list of attachments in the upload cache back to the client side JavaScript 269 # which renders then the list of currently uploaded attachments 270 @TicketAttachments = $UploadCacheObject->FormIDGetAllFilesMeta( 271 FormID => $GetParam{FormID}, 272 ); 273 274 for my $Attachment (@TicketAttachments) { 275 $Attachment->{Filesize} = $LayoutObject->HumanReadableDataSize( 276 Size => $Attachment->{Filesize}, 277 ); 278 } 279 } 280 281 # Get the first article of the ticket. 282 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 283 my @MetaArticles = $ArticleObject->ArticleList( 284 TicketID => $Self->{TicketID}, 285 UserID => $Self->{UserID}, 286 OnlyFirst => 1, 287 ); 288 my %Article = $ArticleObject->BackendForArticle( %{ $MetaArticles[0] } )->ArticleGet( 289 %{ $MetaArticles[0] }, 290 DynamicFields => 0, 291 ); 292 293 # get the matching signature for the current user 294 my $Signature = $TemplateGenerator->Signature( 295 TicketID => $Self->{TicketID}, 296 ArticleID => $Article{ArticleID}, 297 Data => \%Article, 298 UserID => $Self->{UserID}, 299 ); 300 301 # append the signature to the body text 302 if ( $LayoutObject->{BrowserRichText} ) { 303 $TemplateText = $TemplateText . '<br><br>' . $Signature; 304 } 305 else { 306 $TemplateText = $TemplateText . "\n\n" . $Signature; 307 } 308 309 @TemplateAJAX = ( 310 { 311 Name => 'UseTemplateNote', 312 Data => '0', 313 }, 314 { 315 Name => 'RichText', 316 Data => $TemplateText || '', 317 }, 318 { 319 Name => 'TicketAttachments', 320 Data => \@TicketAttachments, 321 KeepData => 1, 322 }, 323 ); 324 } 325 326 my $JSON = $LayoutObject->BuildSelectionJSON( 327 [ 328 { 329 Name => 'NewStateID', 330 Data => $NextStates, 331 SelectedID => $GetParam{NewStateID}, 332 Translation => 1, 333 PossibleNone => $Config->{StateDefault} ? 0 : 1, 334 Max => 100, 335 }, 336 { 337 Name => 'StandardTemplateID', 338 Data => $StandardTemplates, 339 SelectedID => $GetParam{StandardTemplateID}, 340 PossibleNone => 1, 341 Translation => 1, 342 Max => 100, 343 }, 344 @DynamicFieldAJAX, 345 @TemplateAJAX, 346 ], 347 ); 348 349 return $LayoutObject->Attachment( 350 ContentType => 'application/json; charset=' . $LayoutObject->{Charset}, 351 Content => $JSON, 352 Type => 'inline', 353 NoCache => 1, 354 ); 355 } 356 elsif ( $Self->{Subaction} eq 'AJAXUpdate' ) { 357 $Output = $Self->AjaxUpdate(); 358 } 359 elsif ( $Self->{LoadedFormDraftID} ) { 360 $Output = $Self->SendEmail(); 361 } 362 else { 363 $Output = $Self->Form(); 364 } 365 366 return $Output; 367} 368 369sub Form { 370 my ( $Self, %Param ) = @_; 371 372 my %Error; 373 my %ACLCompatGetParam; 374 375 # get param object 376 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 377 378 # ACL compatibility translation 379 $ACLCompatGetParam{NextStateID} = $ParamObject->GetParam( Param => 'NextStateID' ); 380 381 # get layout object 382 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 383 384 # check needed stuff 385 if ( !$Self->{TicketID} ) { 386 387 return $LayoutObject->ErrorScreen( 388 Message => Translatable('Got no TicketID!'), 389 Comment => Translatable('System Error!'), 390 ); 391 } 392 393 # get needed objects 394 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 395 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 396 397 # get ticket data 398 my %Ticket = $TicketObject->TicketGet( 399 TicketID => $Self->{TicketID}, 400 DynamicFields => 1, 401 ); 402 403 # get config object 404 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 405 406 # get config for frontend module 407 my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}"); 408 409 # check permissions 410 my $Access = $TicketObject->TicketPermission( 411 Type => $Config->{Permission}, 412 TicketID => $Self->{TicketID}, 413 UserID => $Self->{UserID} 414 ); 415 416 # error screen, don't show ticket 417 if ( !$Access ) { 418 419 return $LayoutObject->NoPermission( WithHeader => 'yes' ); 420 } 421 422 my %GetParamExtended = $Self->_GetExtendedParams(); 423 424 my %GetParam = %{ $GetParamExtended{GetParam} }; 425 my @MultipleCustomer = @{ $GetParamExtended{MultipleCustomer} }; 426 my @MultipleCustomerCc = @{ $GetParamExtended{MultipleCustomerCc} }; 427 my @MultipleCustomerBcc = @{ $GetParamExtended{MultipleCustomerBcc} }; 428 429 # get lock state 430 my $Output = ''; 431 if ( $Config->{RequiredLock} ) { 432 if ( !$TicketObject->TicketLockGet( TicketID => $Self->{TicketID} ) ) { 433 434 my $Lock = $TicketObject->TicketLockSet( 435 TicketID => $Self->{TicketID}, 436 Lock => 'lock', 437 UserID => $Self->{UserID} 438 ); 439 440 if ($Lock) { 441 442 # Set new owner if ticket owner is different then logged user. 443 if ( $Ticket{OwnerID} != $Self->{UserID} ) { 444 445 # Remember previous owner, which will be used to restore ticket owner on undo action. 446 $Param{PreviousOwner} = $Ticket{OwnerID}; 447 448 $TicketObject->TicketOwnerSet( 449 TicketID => $Self->{TicketID}, 450 UserID => $Self->{UserID}, 451 NewUserID => $Self->{UserID}, 452 ); 453 } 454 455 # Show lock state. 456 $LayoutObject->Block( 457 Name => 'PropertiesLock', 458 Data => { 459 %Param, 460 TicketID => $Self->{TicketID}, 461 }, 462 ); 463 } 464 } 465 else { 466 my $AccessOk = $TicketObject->OwnerCheck( 467 TicketID => $Self->{TicketID}, 468 OwnerID => $Self->{UserID}, 469 ); 470 if ( !$AccessOk ) { 471 my $Output = $LayoutObject->Header( 472 Type => 'Small', 473 BodyClass => 'Popup', 474 ); 475 $Output .= $LayoutObject->Warning( 476 Message => Translatable('Sorry, you need to be the ticket owner to perform this action.'), 477 Comment => Translatable('Please change the owner first.'), 478 ); 479 $Output .= $LayoutObject->Footer( 480 Type => 'Small', 481 ); 482 483 return $Output; 484 } 485 else { 486 $LayoutObject->Block( 487 Name => 'TicketBack', 488 Data => { 489 %Param, 490 TicketID => $Self->{TicketID}, 491 }, 492 ); 493 } 494 } 495 } 496 else { 497 $LayoutObject->Block( 498 Name => 'TicketBack', 499 Data => { 500 %Param, 501 TicketID => $Self->{TicketID}, 502 }, 503 ); 504 } 505 506 my %Data; 507 508 # Get selected article. 509 if ( $GetParam{ArticleID} ) { 510 my $ArticleBackendObject = $ArticleObject->BackendForArticle( 511 TicketID => $Self->{TicketID}, 512 ArticleID => $GetParam{ArticleID}, 513 ); 514 %Data = $ArticleBackendObject->ArticleGet( 515 TicketID => $Self->{TicketID}, 516 ArticleID => $GetParam{ArticleID}, 517 DynamicFields => 1, 518 ); 519 520 # Check if article is from the same TicketID as we checked permissions for. 521 if ( $Data{TicketID} ne $Self->{TicketID} ) { 522 523 return $LayoutObject->ErrorScreen( 524 Message => $LayoutObject->{LanguageObject} 525 ->Translate( 'Article does not belong to ticket %s!', $Self->{TicketID} ), 526 ); 527 } 528 } 529 530 # Get the last customer article of the ticket. 531 else { 532 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 533 my @MetaArticles = $ArticleObject->ArticleList( 534 TicketID => $Self->{TicketID}, 535 SenderType => 'customer', 536 OnlyLast => 1, 537 UserID => $Self->{UserID}, 538 ); 539 if (@MetaArticles) { 540 %Data = $ArticleObject->BackendForArticle( %{ $MetaArticles[0] } )->ArticleGet( 541 TicketID => $Self->{TicketID}, 542 %{ $MetaArticles[0] }, 543 DynamicFields => 0, 544 ); 545 } 546 } 547 548 # prepare signature 549 my $TemplateGenerator = $Kernel::OM->Get('Kernel::System::TemplateGenerator'); 550 $Data{Signature} = $TemplateGenerator->Signature( 551 TicketID => $Self->{TicketID}, 552 ArticleID => $Data{ArticleID}, 553 Data => \%Data, 554 UserID => $Self->{UserID}, 555 ); 556 557 if ( $GetParam{EmailTemplateID} ) { 558 559 # get template 560 $Data{StdTemplate} = $TemplateGenerator->Template( 561 TicketID => $Self->{TicketID}, 562 ArticleID => $Data{ArticleID}, 563 TemplateID => $GetParam{EmailTemplateID}, 564 Data => \%Data, 565 UserID => $Self->{UserID}, 566 ); 567 568 # get signature 569 $Data{Signature} = $TemplateGenerator->Signature( 570 TicketID => $Self->{TicketID}, 571 Data => \%Data, 572 UserID => $Self->{UserID}, 573 ); 574 } 575 576 # empty the body for preparation 577 $Data{Body} = ''; 578 579 # prepare body for richtext or plaintext content 580 if ( $LayoutObject->{BrowserRichText} ) { 581 582 # add the temp 583 if ( $GetParam{EmailTemplateID} ) { 584 $Data{Body} = $Data{StdTemplate} . '<br/>' . $Data{Body}; 585 } 586 587 $Data{Body} = $Data{Body} . $Data{Signature}; 588 } 589 else { 590 591 # add the temp 592 if ( $GetParam{EmailTemplateID} ) { 593 $Data{Body} = $Data{StdTemplate} . "\n" . $Data{Body}; 594 } 595 596 $Data{Body} = $Data{Body} . $Data{Signature}; 597 598 # prepare body for plain text 599 $Data{Body} =~ s/\t/ /g; 600 } 601 602 # get needed objects 603 my $StdAttachmentObject = $Kernel::OM->Get('Kernel::System::StdAttachment'); 604 my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache'); 605 606 # add std. attachments to email 607 if ( $GetParam{EmailTemplateID} ) { 608 my %AllStdAttachments = $StdAttachmentObject->StdAttachmentStandardTemplateMemberList( 609 StandardTemplateID => $GetParam{EmailTemplateID}, 610 ); 611 for my $ID ( sort keys %AllStdAttachments ) { 612 my %AttachmentsData = $StdAttachmentObject->StdAttachmentGet( ID => $ID ); 613 $UploadCacheObject->FormIDAddFile( 614 FormID => $GetParam{FormID}, 615 %AttachmentsData, 616 ); 617 } 618 } 619 620 # get all attachments meta data 621 my @Attachments = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDGetAllFilesMeta( 622 FormID => $GetParam{FormID}, 623 ); 624 625 # check some values 626 for my $Recipient (qw(To Cc Bcc Subject)) { 627 if ( $Data{$Recipient} ) { 628 delete $Data{$Recipient}; 629 } 630 } 631 632 # put & get attributes like sender address 633 %Data = $TemplateGenerator->Attributes( 634 TicketID => $Self->{TicketID}, 635 ArticleID => $GetParam{ArticleID}, 636 Data => \%Data, 637 UserID => $Self->{UserID}, 638 ); 639 640 # prepare the subject 641 $Data{Subject} =~ s{ \A (?: .*? : \s*) }{}xms; 642 643 # run compose modules 644 if ( ref( $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') ) eq 'HASH' ) { 645 646 # use ticket QueueID in compose modules 647 $GetParam{QueueID} = $Ticket{QueueID}; 648 649 my %Jobs = %{ $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') }; 650 for my $Job ( sort keys %Jobs ) { 651 652 # load module 653 if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( $Jobs{$Job}->{Module} ) ) { 654 655 return $LayoutObject->FatalError(); 656 } 657 my $Object = $Jobs{$Job}->{Module}->new( 658 %{$Self}, 659 Debug => $Self->{Debug}, 660 ); 661 662 # get params 663 PARAMETER: 664 for my $Parameter ( $Object->Option( %GetParam, Config => $Jobs{$Job} ) ) { 665 666 if ( $Jobs{$Job}->{ParamType} && $Jobs{$Job}->{ParamType} ne 'Single' ) { 667 @{ $GetParam{$Parameter} } = $ParamObject->GetArray( Param => $Parameter ); 668 next PARAMETER; 669 } 670 671 $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter ); 672 } 673 674 # run module 675 my $NewParams = $Object->Run( %Data, %GetParam, Config => $Jobs{$Job} ); 676 677 if ($NewParams) { 678 for my $Parameter ( $Object->Option( %GetParam, Config => $Jobs{$Job} ) ) { 679 $GetParam{$Parameter} = $NewParams; 680 } 681 } 682 683 # get errors 684 %Error = ( %Error, $Object->Error( %GetParam, Config => $Jobs{$Job} ) ); 685 } 686 } 687 688 # create HTML strings for all dynamic fields 689 my %DynamicFieldHTML; 690 691 # get the dynamic fields for this screen 692 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 693 Valid => 1, 694 ObjectType => [ 'Ticket', 'Article' ], 695 FieldFilter => $Config->{DynamicField} || {}, 696 ); 697 698 # get dynamic field backend object 699 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 700 701 # cycle through the activated Dynamic Fields for this screen 702 DYNAMICFIELD: 703 for my $DynamicFieldConfig ( @{$DynamicField} ) { 704 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 705 706 my $PossibleValuesFilter; 707 708 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 709 DynamicFieldConfig => $DynamicFieldConfig, 710 Behavior => 'IsACLReducible', 711 ); 712 713 if ($IsACLReducible) { 714 715 # get PossibleValues 716 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 717 DynamicFieldConfig => $DynamicFieldConfig, 718 ); 719 720 # check if field has PossibleValues property in its configuration 721 if ( IsHashRefWithData($PossibleValues) ) { 722 723 # convert possible values key => value to key => key for ACLs using a Hash slice 724 my %AclData = %{$PossibleValues}; 725 @AclData{ keys %AclData } = keys %AclData; 726 727 # set possible values filter from ACLs 728 my $ACL = $TicketObject->TicketAcl( 729 %GetParam, 730 %ACLCompatGetParam, 731 Action => $Self->{Action}, 732 TicketID => $Self->{TicketID}, 733 ReturnType => 'Ticket', 734 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 735 Data => \%AclData, 736 UserID => $Self->{UserID}, 737 ); 738 if ($ACL) { 739 my %Filter = $TicketObject->TicketAclData(); 740 741 # convert Filer key => key back to key => value using map 742 %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} } 743 keys %Filter; 744 } 745 } 746 } 747 748 # to store dynamic field value from database (or undefined) 749 my $Value; 750 751 # only get values for Ticket fields (all screens based on AgentTickeActionCommon 752 # create a new article, then article fields will be always empty at the beginning) 753 if ( $DynamicFieldConfig->{ObjectType} eq 'Ticket' ) { 754 755 # get value stored on the database from Ticket 756 $Value = $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} }; 757 } 758 759 # get field HTML 760 $DynamicFieldHTML{ $DynamicFieldConfig->{Name} } = 761 $DynamicFieldBackendObject->EditFieldRender( 762 DynamicFieldConfig => $DynamicFieldConfig, 763 PossibleValuesFilter => $PossibleValuesFilter, 764 Value => $Value, 765 Mandatory => 766 $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2, 767 LayoutObject => $LayoutObject, 768 ParamObject => $ParamObject, 769 AJAXUpdate => 1, 770 UpdatableFields => $Self->_GetFieldsToUpdate(), 771 ); 772 } 773 774 # build view ... 775 # start with page ... 776 $Output .= $LayoutObject->Header( 777 Value => $Ticket{TicketNumber}, 778 Type => 'Small', 779 BodyClass => 'Popup', 780 ); 781 782 # Inform a user that article subject will be empty if contains only the ticket hook (if nothing is modified). 783 $Output .= $LayoutObject->Notify( 784 Data => Translatable('Article subject will be empty if the subject contains only the ticket hook!'), 785 ); 786 787 $Output .= $Self->_Mask( 788 TicketNumber => $Ticket{TicketNumber}, 789 TicketID => $Self->{TicketID}, 790 Title => $Ticket{Title}, 791 QueueID => $Ticket{QueueID}, 792 NextStates => $Self->_GetNextStates( 793 %GetParam, 794 %ACLCompatGetParam, 795 ), 796 TimeUnitsRequired => ( 797 $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 798 ? 'Validate_Required' 799 : '' 800 ), 801 Errors => \%Error, 802 MultipleCustomer => \@MultipleCustomer, 803 MultipleCustomerCc => \@MultipleCustomerCc, 804 MultipleCustomerBcc => \@MultipleCustomerBcc, 805 Attachments => \@Attachments, 806 %Data, 807 %GetParam, 808 DynamicFieldHTML => \%DynamicFieldHTML, 809 ); 810 $Output .= $LayoutObject->Footer( 811 Type => 'Small', 812 ); 813 814 return $Output; 815} 816 817sub SendEmail { 818 my ( $Self, %Param ) = @_; 819 820 my %Error; 821 my %ACLCompatGetParam; 822 823 # get param object 824 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 825 826 # ACL compatibility translation 827 $ACLCompatGetParam{NextStateID} = $ParamObject->GetParam( Param => 'NextStateID' ); 828 829 my %GetParamExtended = $Self->_GetExtendedParams(); 830 831 my %GetParam = %{ $GetParamExtended{GetParam} }; 832 my @MultipleCustomer = @{ $GetParamExtended{MultipleCustomer} }; 833 my @MultipleCustomerCc = @{ $GetParamExtended{MultipleCustomerCc} }; 834 my @MultipleCustomerBcc = @{ $GetParamExtended{MultipleCustomerBcc} }; 835 836 my %DynamicFieldValues; 837 838 # get config object 839 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 840 841 # get config for frontend module 842 my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}"); 843 844 # get the dynamic fields for this screen 845 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 846 Valid => 1, 847 ObjectType => [ 'Ticket', 'Article' ], 848 FieldFilter => $Config->{DynamicField} || {}, 849 ); 850 851 # get needed objects 852 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 853 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 854 855 # Get and validate draft action. 856 my $FormDraftAction = $ParamObject->GetParam( Param => 'FormDraftAction' ); 857 if ( $FormDraftAction && !$Config->{FormDraft} ) { 858 return $LayoutObject->ErrorScreen( 859 Message => Translatable('FormDraft functionality disabled!'), 860 Comment => Translatable('Please contact the administrator.'), 861 ); 862 } 863 864 my %FormDraftResponse; 865 866 # Check draft name. 867 if ( 868 $FormDraftAction 869 && ( $FormDraftAction eq 'Add' || $FormDraftAction eq 'Update' ) 870 ) 871 { 872 my $Title = $ParamObject->GetParam( Param => 'FormDraftTitle' ); 873 874 # A draft name is required. 875 if ( !$Title ) { 876 877 %FormDraftResponse = ( 878 Success => 0, 879 ErrorMessage => $Kernel::OM->Get('Kernel::Language')->Translate("Draft name is required!"), 880 ); 881 } 882 883 # Chosen draft name must be unique. 884 else { 885 my $FormDraftList = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftListGet( 886 ObjectType => 'Ticket', 887 ObjectID => $Self->{TicketID}, 888 Action => $Self->{Action}, 889 UserID => $Self->{UserID}, 890 ); 891 DRAFT: 892 for my $FormDraft ( @{$FormDraftList} ) { 893 894 # No existing draft with same name. 895 next DRAFT if $Title ne $FormDraft->{Title}; 896 897 # Same name for update on existing draft. 898 if ( 899 $GetParam{FormDraftID} 900 && $FormDraftAction eq 'Update' 901 && $GetParam{FormDraftID} eq $FormDraft->{FormDraftID} 902 ) 903 { 904 next DRAFT; 905 } 906 907 # Another draft with the chosen name already exists. 908 %FormDraftResponse = ( 909 Success => 0, 910 ErrorMessage => $Kernel::OM->Get('Kernel::Language') 911 ->Translate( "FormDraft name %s is already in use!", $Title ), 912 ); 913 last DRAFT; 914 } 915 } 916 } 917 918 # Perform draft action instead of saving form data in ticket/article. 919 if ( $FormDraftAction && !%FormDraftResponse ) { 920 921 # Reset FormDraftID to prevent updating existing draft. 922 if ( $FormDraftAction eq 'Add' && $GetParam{FormDraftID} ) { 923 $ParamObject->{Query}->param( 924 -name => 'FormDraftID', 925 -value => '', 926 ); 927 } 928 929 my $FormDraftActionOk; 930 if ( 931 $FormDraftAction eq 'Add' 932 || 933 ( $FormDraftAction eq 'Update' && $GetParam{FormDraftID} ) 934 ) 935 { 936 $FormDraftActionOk = $ParamObject->SaveFormDraft( 937 UserID => $Self->{UserID}, 938 ObjectType => 'Ticket', 939 ObjectID => $Self->{TicketID}, 940 ); 941 } 942 elsif ( $FormDraftAction eq 'Delete' && $GetParam{FormDraftID} ) { 943 $FormDraftActionOk = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftDelete( 944 FormDraftID => $GetParam{FormDraftID}, 945 UserID => $Self->{UserID}, 946 ); 947 } 948 949 if ($FormDraftActionOk) { 950 $FormDraftResponse{Success} = 1; 951 } 952 else { 953 %FormDraftResponse = ( 954 Success => 0, 955 ErrorMessage => 'Could not perform requested draft action!', 956 ); 957 } 958 } 959 960 if (%FormDraftResponse) { 961 962 # build JSON output 963 my $JSON = $LayoutObject->JSONEncode( 964 Data => \%FormDraftResponse, 965 ); 966 967 # send JSON response 968 return $LayoutObject->Attachment( 969 ContentType => 'application/json; charset=' . $LayoutObject->{Charset}, 970 Content => $JSON, 971 Type => 'inline', 972 NoCache => 1, 973 ); 974 } 975 976 # cycle through the activated Dynamic Fields for this screen 977 DYNAMICFIELD: 978 for my $DynamicFieldConfig ( @{$DynamicField} ) { 979 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 980 981 # extract the dynamic field value from the web request 982 $DynamicFieldValues{ $DynamicFieldConfig->{Name} } = 983 $DynamicFieldBackendObject->EditFieldValueGet( 984 DynamicFieldConfig => $DynamicFieldConfig, 985 ParamObject => $ParamObject, 986 LayoutObject => $LayoutObject, 987 ); 988 } 989 990 # convert dynamic field values into a structure for ACLs 991 my %DynamicFieldACLParameters; 992 DYNAMICFIELD: 993 for my $DynamicFieldItem ( sort keys %DynamicFieldValues ) { 994 next DYNAMICFIELD if !$DynamicFieldItem; 995 next DYNAMICFIELD if !defined $DynamicFieldValues{$DynamicFieldItem}; 996 997 $DynamicFieldACLParameters{ 'DynamicField_' . $DynamicFieldItem } = $DynamicFieldValues{$DynamicFieldItem}; 998 } 999 $GetParam{DynamicField} = \%DynamicFieldACLParameters; 1000 1001 my $QueueID = $Self->{QueueID}; 1002 my %StateData; 1003 1004 if ( $GetParam{ComposeStateID} ) { 1005 %StateData = $Kernel::OM->Get('Kernel::System::State')->StateGet( 1006 ID => $GetParam{ComposeStateID}, 1007 ); 1008 } 1009 1010 my $NextState = $StateData{Name}; 1011 1012 # check pending date 1013 if ( defined $StateData{TypeName} && $StateData{TypeName} =~ /^pending/i ) { 1014 1015 # convert pending date to a datetime object 1016 my $PendingDateTimeObject = $Kernel::OM->Create( 1017 'Kernel::System::DateTime', 1018 ObjectParams => { 1019 %GetParam, 1020 Second => 0, 1021 }, 1022 ); 1023 1024 # get current system epoch 1025 my $CurSystemDateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 1026 1027 if ( !$PendingDateTimeObject || $PendingDateTimeObject < $CurSystemDateTimeObject ) { 1028 $Error{'DateInvalid'} = 'ServerError'; 1029 } 1030 } 1031 1032 # check To 1033 if ( !$GetParam{To} ) { 1034 $Error{'ToInvalid'} = 'ServerError'; 1035 } 1036 1037 # check body 1038 if ( !$GetParam{Body} ) { 1039 $Error{'BodyInvalid'} = 'ServerError'; 1040 } 1041 1042 # check subject 1043 if ( !$GetParam{Subject} ) { 1044 $Error{'SubjectInvalid'} = 'ServerError'; 1045 } 1046 1047 if ( 1048 $ConfigObject->Get('Ticket::Frontend::AccountTime') 1049 && $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 1050 && $GetParam{TimeUnits} eq '' 1051 ) 1052 { 1053 $Error{'TimeUnitsInvalid'} = 'ServerError'; 1054 } 1055 1056 # get ticket object 1057 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 1058 1059 # prepare subject 1060 my %Ticket = $TicketObject->TicketGet( 1061 TicketID => $Self->{TicketID}, 1062 DynamicFields => 1, 1063 ); 1064 1065 # get upload cache object 1066 my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache'); 1067 1068 # get all attachments meta data 1069 my @Attachments = $UploadCacheObject->FormIDGetAllFilesMeta( 1070 FormID => $GetParam{FormID}, 1071 ); 1072 1073 # create HTML strings for all dynamic fields 1074 my %DynamicFieldHTML; 1075 1076 # cycle through the activated Dynamic Fields for this screen 1077 DYNAMICFIELD: 1078 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1079 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1080 1081 my $PossibleValuesFilter; 1082 1083 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 1084 DynamicFieldConfig => $DynamicFieldConfig, 1085 Behavior => 'IsACLReducible', 1086 ); 1087 1088 if ($IsACLReducible) { 1089 1090 # get PossibleValues 1091 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 1092 DynamicFieldConfig => $DynamicFieldConfig, 1093 ); 1094 1095 # check if field has PossibleValues property in its configuration 1096 if ( IsHashRefWithData($PossibleValues) ) { 1097 1098 # convert possible values key => value to key => key for ACLs using a Hash slice 1099 my %AclData = %{$PossibleValues}; 1100 @AclData{ keys %AclData } = keys %AclData; 1101 1102 # set possible values filter from ACLs 1103 my $ACL = $TicketObject->TicketAcl( 1104 %GetParam, 1105 %ACLCompatGetParam, 1106 Action => $Self->{Action}, 1107 TicketID => $Self->{TicketID}, 1108 ReturnType => 'Ticket', 1109 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 1110 Data => \%AclData, 1111 UserID => $Self->{UserID}, 1112 ); 1113 if ($ACL) { 1114 my %Filter = $TicketObject->TicketAclData(); 1115 1116 # convert Filer key => key back to key => value using map 1117 %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} } 1118 keys %Filter; 1119 } 1120 } 1121 } 1122 1123 my $ValidationResult = $DynamicFieldBackendObject->EditFieldValueValidate( 1124 DynamicFieldConfig => $DynamicFieldConfig, 1125 PossibleValuesFilter => $PossibleValuesFilter, 1126 ParamObject => $ParamObject, 1127 Mandatory => 1128 $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2, 1129 ); 1130 1131 if ( !IsHashRefWithData($ValidationResult) ) { 1132 1133 return $LayoutObject->ErrorScreen( 1134 Message => 1135 $LayoutObject->{LanguageObject} 1136 ->Translate( 'Could not perform validation on field %s!', $DynamicFieldConfig->{Label} ), 1137 Comment => Translatable('Please contact the administrator.'), 1138 ); 1139 } 1140 1141 # propagate validation error to the Error variable to be detected by the frontend 1142 if ( $ValidationResult->{ServerError} ) { 1143 $Error{ $DynamicFieldConfig->{Name} } = ' ServerError'; 1144 } 1145 1146 # get field HTML 1147 $DynamicFieldHTML{ $DynamicFieldConfig->{Name} } = 1148 $DynamicFieldBackendObject->EditFieldRender( 1149 DynamicFieldConfig => $DynamicFieldConfig, 1150 PossibleValuesFilter => $PossibleValuesFilter, 1151 Mandatory => 1152 $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2, 1153 ServerError => $ValidationResult->{ServerError} || '', 1154 ErrorMessage => $ValidationResult->{ErrorMessage} || '', 1155 LayoutObject => $LayoutObject, 1156 ParamObject => $ParamObject, 1157 AJAXUpdate => 1, 1158 UpdatableFields => $Self->_GetFieldsToUpdate(), 1159 ); 1160 } 1161 1162 # transform pending time, time stamp based on user time zone 1163 if ( 1164 defined $GetParam{Year} 1165 && defined $GetParam{Month} 1166 && defined $GetParam{Day} 1167 && defined $GetParam{Hour} 1168 && defined $GetParam{Minute} 1169 ) 1170 { 1171 %GetParam = $LayoutObject->TransformDateSelection( 1172 %GetParam, 1173 ); 1174 } 1175 1176 # get check item object 1177 my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); 1178 1179 # check some values 1180 LINE: 1181 for my $Line (qw(To Cc Bcc)) { 1182 next LINE if !$GetParam{$Line}; 1183 for my $Email ( Mail::Address->parse( $GetParam{$Line} ) ) { 1184 if ( !$CheckItemObject->CheckEmail( Address => $Email->address() ) ) { 1185 $Error{ $Line . 'ErrorType' } = $Line . $CheckItemObject->CheckErrorType() . 'ServerErrorMsg'; 1186 $Error{ $Line . 'Invalid' } = 'ServerError'; 1187 } 1188 my $IsLocal = $Kernel::OM->Get('Kernel::System::SystemAddress')->SystemAddressIsLocalAddress( 1189 Address => $Email->address() 1190 ); 1191 if ($IsLocal) { 1192 $Error{ $Line . 'IsLocalAddress' } = 'ServerError'; 1193 } 1194 } 1195 } 1196 1197 # Make sure sender is correct one. See bug#14872 ( https://bugs.otrs.org/show_bug.cgi?id=14872 ). 1198 $GetParam{From} = $Kernel::OM->Get('Kernel::System::TemplateGenerator')->Sender( 1199 QueueID => $Ticket{QueueID}, 1200 UserID => $Self->{UserID}, 1201 ); 1202 1203 if ( $Self->{LoadedFormDraftID} ) { 1204 1205 # Make sure we don't save form if a draft was loaded. 1206 %Error = ( LoadedFormDraft => 1 ); 1207 } 1208 1209 # run compose modules 1210 my %ArticleParam; 1211 1212 if ( ref( $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') ) eq 'HASH' ) { 1213 my %Jobs = %{ $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') }; 1214 for my $Job ( sort keys %Jobs ) { 1215 1216 # load module 1217 if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( $Jobs{$Job}->{Module} ) ) { 1218 return $LayoutObject->FatalError(); 1219 } 1220 my $Object = $Jobs{$Job}->{Module}->new( 1221 %{$Self}, 1222 Debug => $Self->{Debug}, 1223 ); 1224 1225 my $Multiple; 1226 1227 # get params 1228 PARAMETER: 1229 for my $Parameter ( $Object->Option( %GetParam, Config => $Jobs{$Job} ) ) { 1230 if ( $Jobs{$Job}->{ParamType} && $Jobs{$Job}->{ParamType} ne 'Single' ) { 1231 @{ $GetParam{$Parameter} } = $ParamObject->GetArray( Param => $Parameter ); 1232 $Multiple = 1; 1233 next PARAMETER; 1234 } 1235 1236 $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter ); 1237 } 1238 1239 # run module 1240 $Object->Run( 1241 %GetParam, 1242 StoreNew => 1, 1243 Config => $Jobs{$Job} 1244 ); 1245 1246 # get options that have been removed from the selection 1247 # and add them back to the selection so that the submit 1248 # will contain options that were hidden from the agent 1249 my $Key = $Object->Option( %GetParam, Config => $Jobs{$Job} ); 1250 1251 if ( $Object->can('GetOptionsToRemoveAJAX') ) { 1252 my @RemovedOptions = $Object->GetOptionsToRemoveAJAX(%GetParam); 1253 if (@RemovedOptions) { 1254 if ($Multiple) { 1255 for my $RemovedOption (@RemovedOptions) { 1256 push @{ $GetParam{$Key} }, $RemovedOption; 1257 } 1258 } 1259 else { 1260 $GetParam{$Key} = shift @RemovedOptions; 1261 } 1262 } 1263 } 1264 1265 # ticket params 1266 %ArticleParam = ( 1267 %ArticleParam, 1268 $Object->ArticleOption( %GetParam, %ArticleParam, Config => $Jobs{$Job} ), 1269 ); 1270 1271 # get errors 1272 %Error = ( 1273 %Error, 1274 $Object->Error( %GetParam, Config => $Jobs{$Job} ), 1275 ); 1276 } 1277 } 1278 1279 # check if there is an error 1280 if (%Error) { 1281 1282 my $QueueID = $TicketObject->TicketQueueID( TicketID => $Self->{TicketID} ); 1283 my $Output = $LayoutObject->Header( 1284 Type => 'Small', 1285 BodyClass => 'Popup', 1286 ); 1287 1288 # When a draft is loaded, inform a user that article subject will be empty 1289 # if contains only the ticket hook (if nothing is modified). 1290 if ( $Error{LoadedFormDraft} ) { 1291 $Output .= $LayoutObject->Notify( 1292 Data => $LayoutObject->{LanguageObject}->Translate( 1293 'Article subject will be empty if the subject contains only the ticket hook!' 1294 ), 1295 ); 1296 } 1297 1298 $Output .= $Self->_Mask( 1299 TicketNumber => $Ticket{TicketNumber}, 1300 Title => $Ticket{Title}, 1301 TicketID => $Self->{TicketID}, 1302 QueueID => $QueueID, 1303 NextStates => $Self->_GetNextStates( 1304 %GetParam, 1305 %ACLCompatGetParam, 1306 ), 1307 Errors => \%Error, 1308 MultipleCustomer => \@MultipleCustomer, 1309 MultipleCustomerCc => \@MultipleCustomerCc, 1310 MultipleCustomerBcc => \@MultipleCustomerBcc, 1311 Attachments => \@Attachments, 1312 DynamicFieldHTML => \%DynamicFieldHTML, 1313 %GetParam, 1314 ); 1315 $Output .= $LayoutObject->Footer( 1316 Type => 'Small', 1317 ); 1318 return $Output; 1319 } 1320 1321 # replace <OTRS_TICKET_STATE> with next ticket state name 1322 if ($NextState) { 1323 $GetParam{Body} =~ s/(<|<)OTRS_TICKET_STATE(>|>)/$NextState/g; 1324 } 1325 1326 # get pre loaded attachments 1327 my @AttachmentData = $UploadCacheObject->FormIDGetAllFilesData( 1328 FormID => $GetParam{FormID}, 1329 ); 1330 1331 # get submit attachment 1332 my %UploadStuff = $ParamObject->GetUploadAll( 1333 Param => 'FileUpload', 1334 ); 1335 if (%UploadStuff) { 1336 push @AttachmentData, \%UploadStuff; 1337 } 1338 1339 my $MimeType = 'text/plain'; 1340 if ( $LayoutObject->{BrowserRichText} ) { 1341 $MimeType = 'text/html'; 1342 1343 # remove unused inline images 1344 my @NewAttachmentData; 1345 1346 ATTACHMENT: 1347 for my $Attachment (@AttachmentData) { 1348 my $ContentID = $Attachment->{ContentID}; 1349 if ( $ContentID && ( $Attachment->{ContentType} =~ /image/i ) ) { 1350 my $ContentIDHTMLQuote = $LayoutObject->Ascii2Html( 1351 Text => $ContentID, 1352 ); 1353 1354 # workaround for link encode of rich text editor, see bug#5053 1355 my $ContentIDLinkEncode = $LayoutObject->LinkEncode($ContentID); 1356 $GetParam{Body} =~ s/(ContentID=)$ContentIDLinkEncode/$1$ContentID/g; 1357 1358 # ignore attachment if not linked in body 1359 next ATTACHMENT if $GetParam{Body} !~ /(\Q$ContentIDHTMLQuote\E|\Q$ContentID\E)/i; 1360 } 1361 1362 # remember inline images and normal attachments 1363 push @NewAttachmentData, \%{$Attachment}; 1364 } 1365 @AttachmentData = @NewAttachmentData; 1366 1367 # verify HTML document 1368 $GetParam{Body} = $LayoutObject->RichTextDocumentComplete( 1369 String => $GetParam{Body}, 1370 ); 1371 } 1372 1373 # send email 1374 my $To = ''; 1375 1376 KEY: 1377 for my $Key (qw(To Cc Bcc)) { 1378 next KEY if !$GetParam{$Key}; 1379 if ($To) { 1380 $To .= ', '; 1381 } 1382 $To .= $GetParam{$Key}; 1383 } 1384 1385 # Get attributes like sender address. 1386 my %Data = $Kernel::OM->Get('Kernel::System::TemplateGenerator')->Attributes( 1387 TicketID => $Self->{TicketID}, 1388 Data => {}, 1389 UserID => $Self->{UserID}, 1390 ); 1391 1392 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 1393 my $ArticleID = $ArticleObject->BackendForChannel( ChannelName => 'Email' )->ArticleSend( 1394 SenderType => 'agent', 1395 IsVisibleForCustomer => $GetParam{IsVisibleForCustomer} // 0, 1396 TicketID => $Self->{TicketID}, 1397 HistoryType => 'EmailAgent', 1398 HistoryComment => "\%\%$To", 1399 From => $Data{From}, 1400 To => $GetParam{To}, 1401 Cc => $GetParam{Cc}, 1402 Bcc => $GetParam{Bcc}, 1403 Subject => $GetParam{Subject}, 1404 UserID => $Self->{UserID}, 1405 Body => $GetParam{Body}, 1406 1407 # We start a new communication here, so don't send any references. 1408 # This might lead to information disclosure (domain names; see bug#11246). 1409 InReplyTo => '', 1410 References => '', 1411 Charset => $LayoutObject->{UserCharset}, 1412 MimeType => $MimeType, 1413 Attachment => \@AttachmentData, 1414 %ArticleParam, 1415 ); 1416 1417 # error page 1418 if ( !$ArticleID ) { 1419 1420 return $LayoutObject->ErrorScreen( 1421 Comment => Translatable('Please contact the administrator.'), 1422 ); 1423 } 1424 1425 # time accounting 1426 if ( $GetParam{TimeUnits} ) { 1427 $TicketObject->TicketAccountTime( 1428 TicketID => $Self->{TicketID}, 1429 ArticleID => $ArticleID, 1430 TimeUnit => $GetParam{TimeUnits}, 1431 UserID => $Self->{UserID}, 1432 ); 1433 } 1434 1435 # set dynamic fields 1436 # cycle through the activated Dynamic Fields for this screen 1437 DYNAMICFIELD: 1438 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1439 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1440 1441 # set the object ID (TicketID or ArticleID) depending on the field configuration 1442 my $ObjectID = $DynamicFieldConfig->{ObjectType} eq 'Article' ? $ArticleID : $Self->{TicketID}; 1443 1444 # set the value 1445 my $Success = $DynamicFieldBackendObject->ValueSet( 1446 DynamicFieldConfig => $DynamicFieldConfig, 1447 ObjectID => $ObjectID, 1448 Value => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 1449 UserID => $Self->{UserID}, 1450 ); 1451 } 1452 1453 # set state 1454 if ($NextState) { 1455 $TicketObject->TicketStateSet( 1456 TicketID => $Self->{TicketID}, 1457 ArticleID => $ArticleID, 1458 State => $NextState, 1459 UserID => $Self->{UserID}, 1460 ); 1461 1462 # should I set an unlock? 1463 if ( $StateData{TypeName} =~ /^close/i ) { 1464 $TicketObject->TicketLockSet( 1465 TicketID => $Self->{TicketID}, 1466 Lock => 'unlock', 1467 UserID => $Self->{UserID}, 1468 ); 1469 } 1470 1471 # set pending time 1472 elsif ( $StateData{TypeName} =~ /^pending/i ) { 1473 $TicketObject->TicketPendingTimeSet( 1474 UserID => $Self->{UserID}, 1475 TicketID => $Self->{TicketID}, 1476 %GetParam, 1477 ); 1478 } 1479 } 1480 1481 # remove pre-submitted attachments 1482 $UploadCacheObject->FormIDRemove( FormID => $GetParam{FormID} ); 1483 1484 # If form was called based on a draft, 1485 # delete draft since its content has now been used. 1486 if ( 1487 $GetParam{FormDraftID} 1488 && !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftDelete( 1489 FormDraftID => $GetParam{FormDraftID}, 1490 UserID => $Self->{UserID}, 1491 ) 1492 ) 1493 { 1494 return $LayoutObject->ErrorScreen( 1495 Message => Translatable('Could not delete draft!'), 1496 Comment => Translatable('Please contact the administrator.'), 1497 ); 1498 } 1499 1500 # redirect 1501 if ( 1502 defined $StateData{TypeName} 1503 && $StateData{TypeName} =~ /^close/i 1504 && !$ConfigObject->Get('Ticket::Frontend::RedirectAfterCloseDisabled') 1505 ) 1506 { 1507 return $LayoutObject->PopupClose( 1508 URL => ( $Self->{LastScreenOverview} || 'Action=AgentDashboard' ), 1509 ); 1510 } 1511 1512 return $LayoutObject->PopupClose( 1513 URL => "Action=AgentTicketZoom;TicketID=$Self->{TicketID};ArticleID=$ArticleID", 1514 ); 1515} 1516 1517sub AjaxUpdate { 1518 my ( $Self, %Param ) = @_; 1519 1520 my %Error; 1521 my %ACLCompatGetParam; 1522 1523 # get param object 1524 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 1525 1526 # ACL compatibility translation 1527 $ACLCompatGetParam{NextStateID} = $ParamObject->GetParam( Param => 'NextStateID' ); 1528 1529 my %GetParamExtended = $Self->_GetExtendedParams(); 1530 1531 my %GetParam = %{ $GetParamExtended{GetParam} }; 1532 my @MultipleCustomer = @{ $GetParamExtended{MultipleCustomer} }; 1533 my @MultipleCustomerCc = @{ $GetParamExtended{MultipleCustomerCc} }; 1534 my @MultipleCustomerBcc = @{ $GetParamExtended{MultipleCustomerBcc} }; 1535 1536 my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet( TicketID => $Self->{TicketID} ); 1537 1538 # Make sure sender is correct one. See bug#14872 ( https://bugs.otrs.org/show_bug.cgi?id=14872 ). 1539 $GetParam{From} = $Kernel::OM->Get('Kernel::System::TemplateGenerator')->Sender( 1540 QueueID => $Ticket{QueueID}, 1541 UserID => $Self->{UserID}, 1542 ); 1543 1544 my @ExtendedData; 1545 1546 # get config object 1547 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1548 1549 # run compose modules 1550 if ( ref $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') eq 'HASH' ) { 1551 my %Jobs = %{ $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') }; 1552 1553 JOB: 1554 for my $Job ( sort keys %Jobs ) { 1555 1556 # load module 1557 next JOB if !$Kernel::OM->Get('Kernel::System::Main')->Require( $Jobs{$Job}->{Module} ); 1558 1559 my $Object = $Jobs{$Job}->{Module}->new( 1560 %{$Self}, 1561 Debug => $Self->{Debug}, 1562 ); 1563 1564 my $Multiple; 1565 1566 # get params 1567 PARAMETER: 1568 for my $Parameter ( $Object->Option( %GetParam, Config => $Jobs{$Job} ) ) { 1569 if ( $Jobs{$Job}->{ParamType} && $Jobs{$Job}->{ParamType} ne 'Single' ) { 1570 @{ $GetParam{$Parameter} } = $ParamObject->GetArray( Param => $Parameter ); 1571 $Multiple = 1; 1572 next PARAMETER; 1573 } 1574 1575 $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter ); 1576 } 1577 1578 # run module 1579 my %Data = $Object->Data( %GetParam, Config => $Jobs{$Job} ); 1580 1581 # get AJAX param values 1582 if ( $Object->can('GetParamAJAX') ) { 1583 %GetParam = ( %GetParam, $Object->GetParamAJAX(%GetParam) ); 1584 } 1585 1586 # get options that have to be removed from the selection visible 1587 # to the agent. These options will be added again on submit. 1588 if ( $Object->can('GetOptionsToRemoveAJAX') ) { 1589 my @OptionsToRemove = $Object->GetOptionsToRemoveAJAX(%GetParam); 1590 1591 for my $OptionToRemove (@OptionsToRemove) { 1592 delete $Data{$OptionToRemove}; 1593 } 1594 } 1595 1596 my $Key = $Object->Option( %GetParam, Config => $Jobs{$Job} ); 1597 if ($Key) { 1598 push( 1599 @ExtendedData, 1600 { 1601 Name => $Key, 1602 Data => \%Data, 1603 SelectedID => $GetParam{$Key}, 1604 Translation => 1, 1605 PossibleNone => 1, 1606 Multiple => $Multiple, 1607 Max => 100, 1608 } 1609 ); 1610 } 1611 } 1612 } 1613 1614 my %DynamicFieldValues; 1615 1616 # get config for frontend module 1617 my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}"); 1618 1619 # get the dynamic fields for this screen 1620 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 1621 Valid => 1, 1622 ObjectType => [ 'Ticket', 'Article' ], 1623 FieldFilter => $Config->{DynamicField} || {}, 1624 ); 1625 1626 # get needed objects 1627 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 1628 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 1629 1630 # cycle through the activated Dynamic Fields for this screen 1631 DYNAMICFIELD: 1632 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1633 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1634 1635 # extract the dynamic field value from the web request 1636 $DynamicFieldValues{ $DynamicFieldConfig->{Name} } = 1637 $DynamicFieldBackendObject->EditFieldValueGet( 1638 DynamicFieldConfig => $DynamicFieldConfig, 1639 ParamObject => $ParamObject, 1640 LayoutObject => $LayoutObject, 1641 ); 1642 } 1643 1644 # convert dynamic field values into a structure for ACLs 1645 my %DynamicFieldACLParameters; 1646 DYNAMICFIELD: 1647 for my $DynamicFieldItem ( sort keys %DynamicFieldValues ) { 1648 next DYNAMICFIELD if !$DynamicFieldItem; 1649 next DYNAMICFIELD if !defined $DynamicFieldValues{$DynamicFieldItem}; 1650 1651 $DynamicFieldACLParameters{ 'DynamicField_' . $DynamicFieldItem } = $DynamicFieldValues{$DynamicFieldItem}; 1652 } 1653 $GetParam{DynamicField} = \%DynamicFieldACLParameters; 1654 1655 my $NextStates = $Self->_GetNextStates( 1656 %GetParam, 1657 %ACLCompatGetParam, 1658 ); 1659 1660 # update Dynamic Fields Possible Values via AJAX 1661 my @DynamicFieldAJAX; 1662 1663 # cycle through the activated Dynamic Fields for this screen 1664 DYNAMICFIELD: 1665 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1666 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1667 1668 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 1669 DynamicFieldConfig => $DynamicFieldConfig, 1670 Behavior => 'IsACLReducible', 1671 ); 1672 next DYNAMICFIELD if !$IsACLReducible; 1673 1674 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 1675 DynamicFieldConfig => $DynamicFieldConfig, 1676 ); 1677 1678 # convert possible values key => value to key => key for ACLs using a Hash slice 1679 my %AclData = %{$PossibleValues}; 1680 @AclData{ keys %AclData } = keys %AclData; 1681 1682 # get ticket object 1683 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 1684 1685 # set possible values filter from ACLs 1686 my $ACL = $TicketObject->TicketAcl( 1687 %GetParam, 1688 %ACLCompatGetParam, 1689 Action => $Self->{Action}, 1690 TicketID => $Self->{TicketID}, 1691 QueueID => $Self->{QueueID}, 1692 ReturnType => 'Ticket', 1693 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 1694 Data => \%AclData, 1695 UserID => $Self->{UserID}, 1696 ); 1697 if ($ACL) { 1698 my %Filter = $TicketObject->TicketAclData(); 1699 1700 # convert Filer key => key back to key => value using map 1701 %{$PossibleValues} = map { $_ => $PossibleValues->{$_} } keys %Filter; 1702 } 1703 1704 my $DataValues = $DynamicFieldBackendObject->BuildSelectionDataGet( 1705 DynamicFieldConfig => $DynamicFieldConfig, 1706 PossibleValues => $PossibleValues, 1707 Value => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 1708 ) || $PossibleValues; 1709 1710 # add dynamic field to the list of fields to update 1711 push( 1712 @DynamicFieldAJAX, 1713 { 1714 Name => 'DynamicField_' . $DynamicFieldConfig->{Name}, 1715 Data => $DataValues, 1716 SelectedID => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 1717 Translation => $DynamicFieldConfig->{Config}->{TranslatableValues} || 0, 1718 Max => 100, 1719 } 1720 ); 1721 } 1722 1723 my $JSON = $LayoutObject->BuildSelectionJSON( 1724 [ 1725 { 1726 Name => 'ComposeStateID', 1727 Data => $NextStates, 1728 SelectedID => $GetParam{ComposeStateID}, 1729 Translation => 1, 1730 PossibleNone => 1, 1731 Max => 100, 1732 }, 1733 @ExtendedData, 1734 @DynamicFieldAJAX, 1735 ], 1736 ); 1737 1738 return $LayoutObject->Attachment( 1739 ContentType => 'application/json; charset=' . $LayoutObject->{Charset}, 1740 Content => $JSON, 1741 Type => 'inline', 1742 NoCache => 1, 1743 ); 1744} 1745 1746sub _GetNextStates { 1747 my ( $Self, %Param ) = @_; 1748 1749 # get next states 1750 my %NextStates = $Kernel::OM->Get('Kernel::System::Ticket')->TicketStateList( 1751 %Param, 1752 Action => $Self->{Action}, 1753 TicketID => $Self->{TicketID}, 1754 UserID => $Self->{UserID}, 1755 ); 1756 1757 return \%NextStates; 1758} 1759 1760sub _Mask { 1761 my ( $Self, %Param ) = @_; 1762 1763 my $DynamicFieldNames = $Self->_GetFieldsToUpdate( 1764 OnlyDynamicFields => 1 1765 ); 1766 1767 # get config object 1768 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1769 1770 # get config for frontend module 1771 my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}"); 1772 1773 # build next states string 1774 my %State; 1775 if ( !$Param{ComposeStateID} ) { 1776 $State{SelectedValue} = $Config->{StateDefault}; 1777 } 1778 else { 1779 $State{SelectedID} = $Param{ComposeStateID}; 1780 } 1781 1782 # get layout object 1783 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 1784 1785 $Param{NextStatesStrg} = $LayoutObject->BuildSelection( 1786 Data => $Param{NextStates}, 1787 Name => 'ComposeStateID', 1788 PossibleNone => 1, 1789 Class => 'Modernize', 1790 %State, 1791 ); 1792 1793 if ( !$Param{IsVisibleForCustomerPresent} ) { 1794 $Param{IsVisibleForCustomer} = $Config->{IsVisibleForCustomerDefault}; 1795 } 1796 1797 # prepare errors! 1798 if ( $Param{Errors} ) { 1799 for my $Error ( sort keys %{ $Param{Errors} } ) { 1800 $Param{$Error} = $LayoutObject->Ascii2Html( Text => $Param{Errors}->{$Error} ); 1801 } 1802 } 1803 1804 # pending data string 1805 $Param{PendingDateString} = $LayoutObject->BuildDateSelection( 1806 %Param, 1807 YearPeriodPast => 0, 1808 YearPeriodFuture => 5, 1809 Format => 'DateInputFormatLong', 1810 DiffTime => $ConfigObject->Get('Ticket::Frontend::PendingDiffTime') || 0, 1811 Class => $Param{Errors}->{DateInvalid} || ' ', 1812 Validate => 1, 1813 ValidateDateInFuture => 1, 1814 ); 1815 1816 # Multiple-Autocomplete 1817 # Cc 1818 my $CustomerCounterCc = 0; 1819 if ( $Param{MultipleCustomerCc} ) { 1820 for my $Item ( @{ $Param{MultipleCustomerCc} } ) { 1821 $LayoutObject->Block( 1822 Name => 'CcMultipleCustomer', 1823 Data => $Item, 1824 ); 1825 $LayoutObject->Block( 1826 Name => 'Cc' . $Item->{CustomerErrorMsg}, 1827 Data => $Item, 1828 ); 1829 if ( $Item->{CustomerError} ) { 1830 $LayoutObject->Block( 1831 Name => 'CcCustomerErrorExplantion', 1832 ); 1833 } 1834 $CustomerCounterCc++; 1835 } 1836 } 1837 1838 if ( !$CustomerCounterCc ) { 1839 $Param{CcCustomerHiddenContainer} = 'Hidden'; 1840 } 1841 1842 # set customer counter 1843 $LayoutObject->Block( 1844 Name => 'CcMultipleCustomerCounter', 1845 Data => { 1846 CustomerCounter => $CustomerCounterCc++, 1847 }, 1848 ); 1849 1850 # Bcc 1851 my $CustomerCounterBcc = 0; 1852 if ( $Param{MultipleCustomerBcc} ) { 1853 for my $Item ( @{ $Param{MultipleCustomerBcc} } ) { 1854 $LayoutObject->Block( 1855 Name => 'BccMultipleCustomer', 1856 Data => $Item, 1857 ); 1858 $LayoutObject->Block( 1859 Name => 'Bcc' . $Item->{CustomerErrorMsg}, 1860 Data => $Item, 1861 ); 1862 if ( $Item->{CustomerError} ) { 1863 $LayoutObject->Block( 1864 Name => 'BccCustomerErrorExplantion', 1865 ); 1866 } 1867 $CustomerCounterBcc++; 1868 } 1869 } 1870 1871 if ( !$CustomerCounterBcc ) { 1872 $Param{BccCustomerHiddenContainer} = 'Hidden'; 1873 } 1874 1875 # set customer counter 1876 $LayoutObject->Block( 1877 Name => 'BccMultipleCustomerCounter', 1878 Data => { 1879 CustomerCounter => $CustomerCounterBcc++, 1880 }, 1881 ); 1882 1883 # To 1884 my $CustomerCounter = 0; 1885 if ( $Param{MultipleCustomer} ) { 1886 for my $Item ( @{ $Param{MultipleCustomer} } ) { 1887 $LayoutObject->Block( 1888 Name => 'MultipleCustomer', 1889 Data => $Item, 1890 ); 1891 $LayoutObject->Block( 1892 Name => $Item->{CustomerErrorMsg}, 1893 Data => $Item, 1894 ); 1895 if ( $Item->{CustomerError} ) { 1896 $LayoutObject->Block( 1897 Name => 'CustomerErrorExplantion', 1898 ); 1899 } 1900 $CustomerCounter++; 1901 } 1902 } 1903 1904 if ( !$CustomerCounter ) { 1905 $Param{CustomerHiddenContainer} = 'Hidden'; 1906 } 1907 1908 # set customer counter 1909 $LayoutObject->Block( 1910 Name => 'MultipleCustomerCounter', 1911 Data => { 1912 CustomerCounter => $CustomerCounter++, 1913 }, 1914 ); 1915 1916 if ( $Param{ToInvalid} && $Param{Errors} && !$Param{Errors}->{ToErrorType} ) { 1917 $LayoutObject->Block( 1918 Name => 'ToServerErrorMsg', 1919 ); 1920 } 1921 1922 if ( $Param{ToIsLocalAddress} && $Param{Errors} && !$Param{Errors}->{ToErrorType} ) { 1923 $LayoutObject->Block( 1924 Name => 'ToIsLocalAddressServerErrorMsg', 1925 Data => \%Param, 1926 ); 1927 } 1928 1929 if ( $Param{CcInvalid} && $Param{Errors} && !$Param{Errors}->{CcErrorType} ) { 1930 $LayoutObject->Block( 1931 Name => 'CcServerErrorMsg', 1932 ); 1933 } 1934 1935 if ( $Param{CcIsLocalAddress} && $Param{Errors} && !$Param{Errors}->{CcErrorType} ) { 1936 $LayoutObject->Block( 1937 Name => 'CcIsLocalAddressServerErrorMsg', 1938 Data => \%Param, 1939 ); 1940 } 1941 1942 if ( $Param{BccInvalid} && $Param{Errors} && !$Param{Errors}->{BccErrorType} ) { 1943 $LayoutObject->Block( 1944 Name => 'BccServerErrorMsg', 1945 ); 1946 } 1947 1948 if ( $Param{BccIsLocalAddress} && $Param{Errors} && !$Param{Errors}->{BccErrorType} ) { 1949 $LayoutObject->Block( 1950 Name => 'BccIsLocalAddressServerErrorMsg', 1951 Data => \%Param, 1952 ); 1953 } 1954 1955 # get the dynamic fields for this screen 1956 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 1957 Valid => 1, 1958 ObjectType => [ 'Ticket', 'Article' ], 1959 FieldFilter => $Config->{DynamicField} || {}, 1960 ); 1961 1962 # Dynamic fields 1963 # cycle through the activated Dynamic Fields for this screen 1964 DYNAMICFIELD: 1965 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1966 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1967 1968 # skip fields that HTML could not be retrieved 1969 next DYNAMICFIELD if !IsHashRefWithData( 1970 $Param{DynamicFieldHTML}->{ $DynamicFieldConfig->{Name} } 1971 ); 1972 1973 # get the HTML strings form $Param 1974 my $DynamicFieldHTML = $Param{DynamicFieldHTML}->{ $DynamicFieldConfig->{Name} }; 1975 1976 $LayoutObject->Block( 1977 Name => 'DynamicField', 1978 Data => { 1979 Name => $DynamicFieldConfig->{Name}, 1980 Label => $DynamicFieldHTML->{Label}, 1981 Field => $DynamicFieldHTML->{Field}, 1982 }, 1983 ); 1984 1985 # example of dynamic fields order customization 1986 $LayoutObject->Block( 1987 Name => 'DynamicField_' . $DynamicFieldConfig->{Name}, 1988 Data => { 1989 Name => $DynamicFieldConfig->{Name}, 1990 Label => $DynamicFieldHTML->{Label}, 1991 Field => $DynamicFieldHTML->{Field}, 1992 }, 1993 ); 1994 } 1995 1996 # show time accounting box 1997 if ( $ConfigObject->Get('Ticket::Frontend::AccountTime') ) { 1998 if ( $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') ) { 1999 $LayoutObject->Block( 2000 Name => 'TimeUnitsLabelMandatory', 2001 Data => \%Param, 2002 ); 2003 } 2004 else { 2005 $LayoutObject->Block( 2006 Name => 'TimeUnitsLabel', 2007 Data => \%Param, 2008 ); 2009 } 2010 $LayoutObject->Block( 2011 Name => 'TimeUnits', 2012 Data => \%Param, 2013 ); 2014 } 2015 2016 # Show the customer user address book if the module is registered and java script support is available. 2017 if ( 2018 $ConfigObject->Get('Frontend::Module')->{AgentCustomerUserAddressBook} 2019 && $LayoutObject->{BrowserJavaScriptSupport} 2020 ) 2021 { 2022 $Param{OptionCustomerUserAddressBook} = 1; 2023 } 2024 2025 # build text template string 2026 my %StandardTemplates = $Kernel::OM->Get('Kernel::System::StandardTemplate')->StandardTemplateList( 2027 Valid => 1, 2028 Type => 'Email', 2029 ); 2030 2031 my $QueueStandardTemplates = $Self->_GetStandardTemplates( 2032 %Param, 2033 TicketID => $Self->{TicketID} || '', 2034 ); 2035 2036 if ( 2037 IsHashRefWithData( 2038 $QueueStandardTemplates 2039 || ( $Param{Queue} && IsHashRefWithData( \%StandardTemplates ) ) 2040 ) 2041 ) 2042 { 2043 $Param{StandardTemplateStrg} = $LayoutObject->BuildSelection( 2044 Data => $QueueStandardTemplates || {}, 2045 Name => 'StandardTemplateID', 2046 SelectedID => $Param{StandardTemplateID} || '', 2047 Class => 'Modernize', 2048 PossibleNone => 1, 2049 Sort => 'AlphanumericValue', 2050 Translation => 1, 2051 Max => 200, 2052 ); 2053 $LayoutObject->Block( 2054 Name => 'StandardTemplate', 2055 Data => {%Param}, 2056 ); 2057 } 2058 2059 # show attachments 2060 ATTACHMENT: 2061 for my $Attachment ( @{ $Param{Attachments} } ) { 2062 if ( 2063 $Attachment->{ContentID} 2064 && $LayoutObject->{BrowserRichText} 2065 && ( $Attachment->{ContentType} =~ /image/i ) 2066 ) 2067 { 2068 next ATTACHMENT; 2069 } 2070 2071 push @{ $Param{AttachmentList} }, $Attachment; 2072 } 2073 2074 # add rich text editor 2075 if ( $LayoutObject->{BrowserRichText} ) { 2076 2077 # use height/width defined for this screen 2078 $Param{RichTextHeight} = $Config->{RichTextHeight} || 0; 2079 $Param{RichTextWidth} = $Config->{RichTextWidth} || 0; 2080 2081 # set up rich text editor 2082 $LayoutObject->SetRichTextParameters( 2083 Data => \%Param, 2084 ); 2085 } 2086 2087 $LayoutObject->AddJSData( 2088 Key => 'DynamicFieldNames', 2089 Value => $DynamicFieldNames, 2090 ); 2091 2092 my $LoadedFormDraft; 2093 if ( $Self->{LoadedFormDraftID} ) { 2094 $LoadedFormDraft = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftGet( 2095 FormDraftID => $Self->{LoadedFormDraftID}, 2096 GetContent => 0, 2097 UserID => $Self->{UserID}, 2098 ); 2099 2100 my @Articles = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleList( 2101 TicketID => $Self->{TicketID}, 2102 OnlyLast => 1, 2103 ); 2104 2105 if (@Articles) { 2106 my $LastArticle = $Articles[0]; 2107 2108 my $LastArticleSystemTime; 2109 if ( $LastArticle->{CreateTime} ) { 2110 my $LastArticleSystemTimeObject = $Kernel::OM->Create( 2111 'Kernel::System::DateTime', 2112 ObjectParams => { 2113 String => $LastArticle->{CreateTime}, 2114 }, 2115 ); 2116 $LastArticleSystemTime = $LastArticleSystemTimeObject->ToEpoch(); 2117 } 2118 2119 my $FormDraftSystemTimeObject = $Kernel::OM->Create( 2120 'Kernel::System::DateTime', 2121 ObjectParams => { 2122 String => $LoadedFormDraft->{ChangeTime}, 2123 }, 2124 ); 2125 my $FormDraftSystemTime = $FormDraftSystemTimeObject->ToEpoch(); 2126 2127 if ( !$LastArticleSystemTime || $FormDraftSystemTime <= $LastArticleSystemTime ) { 2128 $Param{FormDraftOutdated} = 1; 2129 } 2130 } 2131 } 2132 2133 if ( IsHashRefWithData($LoadedFormDraft) ) { 2134 2135 $LoadedFormDraft->{ChangeByName} = $Kernel::OM->Get('Kernel::System::User')->UserName( 2136 UserID => $LoadedFormDraft->{ChangeBy}, 2137 ); 2138 } 2139 2140 # create & return output 2141 return $LayoutObject->Output( 2142 TemplateFile => 'AgentTicketEmailOutbound', 2143 Data => { 2144 %Param, 2145 FormDraft => $Config->{FormDraft}, 2146 FormDraftID => $Self->{LoadedFormDraftID}, 2147 FormDraftTitle => $LoadedFormDraft ? $LoadedFormDraft->{Title} : '', 2148 FormDraftMeta => $LoadedFormDraft, 2149 }, 2150 ); 2151} 2152 2153sub _GetFieldsToUpdate { 2154 my ( $Self, %Param ) = @_; 2155 2156 my @UpdatableFields; 2157 2158 # set the fields that can be updateable via AJAXUpdate 2159 if ( !$Param{OnlyDynamicFields} ) { 2160 @UpdatableFields = qw( ComposeStateID ); 2161 } 2162 2163 # get config for frontend module 2164 my $Config = $Kernel::OM->Get('Kernel::Config')->Get("Ticket::Frontend::$Self->{Action}"); 2165 2166 # get the dynamic fields for this screen 2167 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 2168 Valid => 1, 2169 ObjectType => [ 'Ticket', 'Article' ], 2170 FieldFilter => $Config->{DynamicField} || {}, 2171 ); 2172 2173 # cycle through the activated Dynamic Fields for this screen 2174 DYNAMICFIELD: 2175 for my $DynamicFieldConfig ( @{$DynamicField} ) { 2176 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 2177 2178 my $IsACLReducible = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->HasBehavior( 2179 DynamicFieldConfig => $DynamicFieldConfig, 2180 Behavior => 'IsACLReducible', 2181 ); 2182 next DYNAMICFIELD if !$IsACLReducible; 2183 2184 push @UpdatableFields, 'DynamicField_' . $DynamicFieldConfig->{Name}; 2185 } 2186 2187 return \@UpdatableFields; 2188} 2189 2190sub _GetExtendedParams { 2191 my ( $Self, %Param ) = @_; 2192 2193 # get param object 2194 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 2195 2196 # get params 2197 my %GetParam; 2198 for my $Key ( 2199 qw(To Cc Bcc Subject Body ComposeStateID IsVisibleForCustomer IsVisibleForCustomerPresent 2200 ArticleID TimeUnits Year Month Day Hour Minute FormID FormDraftID Title) 2201 ) 2202 { 2203 my $Value = $ParamObject->GetParam( Param => $Key ); 2204 if ( defined $Value ) { 2205 $GetParam{$Key} = $Value; 2206 } 2207 } 2208 2209 $GetParam{EmailTemplateID} = $ParamObject->GetParam( Param => 'EmailTemplateID' ) || ''; 2210 2211 # create form id 2212 if ( !$GetParam{FormID} ) { 2213 $GetParam{FormID} = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDCreate(); 2214 } 2215 2216 # hash for check duplicated entries 2217 my %AddressesList; 2218 my @MultipleCustomer; 2219 my $CustomersNumber = $ParamObject->GetParam( Param => 'CustomerTicketCounterToCustomer' ) || 0; 2220 my $Selected = $ParamObject->GetParam( Param => 'CustomerSelected' ) || ''; 2221 2222 # get check item object 2223 my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); 2224 2225 if ($CustomersNumber) { 2226 my $CustomerCounter = 1; 2227 for my $Count ( 1 ... $CustomersNumber ) { 2228 my $CustomerElement = $ParamObject->GetParam( Param => 'CustomerTicketText_' . $Count ); 2229 my $CustomerSelected = ( $Selected eq $Count ? 'checked="checked"' : '' ); 2230 my $CustomerKey = $ParamObject->GetParam( Param => 'CustomerKey_' . $Count ) 2231 || ''; 2232 my $CustomerQueue = $ParamObject->GetParam( Param => 'CustomerQueue_' . $Count ) 2233 || ''; 2234 if ($CustomerElement) { 2235 2236 if ( $GetParam{To} ) { 2237 $GetParam{To} .= ', ' . $CustomerElement; 2238 } 2239 else { 2240 $GetParam{To} = $CustomerElement; 2241 } 2242 2243 # check email address 2244 my $CustomerErrorMsg = 'CustomerGenericServerErrorMsg'; 2245 my $CustomerError = ''; 2246 for my $Email ( Mail::Address->parse($CustomerElement) ) { 2247 if ( !$CheckItemObject->CheckEmail( Address => $Email->address() ) ) { 2248 $CustomerErrorMsg = $CheckItemObject->CheckErrorType() 2249 . 'ServerErrorMsg'; 2250 $CustomerError = 'ServerError'; 2251 } 2252 } 2253 2254 # check for duplicated entries 2255 if ( defined $AddressesList{$CustomerElement} && $CustomerError eq '' ) { 2256 $CustomerErrorMsg = 'IsDuplicatedServerErrorMsg'; 2257 $CustomerError = 'ServerError'; 2258 } 2259 2260 my $CustomerDisabled = ''; 2261 my $CountAux = $CustomerCounter++; 2262 if ( $CustomerError ne '' ) { 2263 $CustomerDisabled = 'disabled="disabled"'; 2264 $CountAux = $Count . 'Error'; 2265 } 2266 2267 if ( $CustomerQueue ne '' ) { 2268 $CustomerQueue = $Count; 2269 } 2270 2271 push @MultipleCustomer, { 2272 Count => $CountAux, 2273 CustomerElement => $CustomerElement, 2274 CustomerSelected => $CustomerSelected, 2275 CustomerKey => $CustomerKey, 2276 CustomerError => $CustomerError, 2277 CustomerErrorMsg => $CustomerErrorMsg, 2278 CustomerDisabled => $CustomerDisabled, 2279 CustomerQueue => $CustomerQueue, 2280 }; 2281 $AddressesList{$CustomerElement} = 1; 2282 } 2283 } 2284 } 2285 2286 my @MultipleCustomerCc; 2287 my $CustomersNumberCc = $ParamObject->GetParam( Param => 'CustomerTicketCounterCcCustomer' ) || 0; 2288 2289 if ($CustomersNumberCc) { 2290 my $CustomerCounterCc = 1; 2291 for my $Count ( 1 ... $CustomersNumberCc ) { 2292 my $CustomerElementCc = $ParamObject->GetParam( Param => 'CcCustomerTicketText_' . $Count ); 2293 my $CustomerKeyCc = $ParamObject->GetParam( Param => 'CcCustomerKey_' . $Count ) 2294 || ''; 2295 my $CustomerQueueCc = $ParamObject->GetParam( Param => 'CcCustomerQueue_' . $Count ) 2296 || ''; 2297 2298 if ($CustomerElementCc) { 2299 2300 if ( $GetParam{Cc} ) { 2301 $GetParam{Cc} .= ', ' . $CustomerElementCc; 2302 } 2303 else { 2304 $GetParam{Cc} = $CustomerElementCc; 2305 } 2306 2307 # check email address 2308 my $CustomerErrorMsgCc = 'CustomerGenericServerErrorMsg'; 2309 my $CustomerErrorCc = ''; 2310 for my $Email ( Mail::Address->parse($CustomerElementCc) ) { 2311 if ( !$CheckItemObject->CheckEmail( Address => $Email->address() ) ) { 2312 $CustomerErrorMsgCc = $CheckItemObject->CheckErrorType() 2313 . 'ServerErrorMsg'; 2314 $CustomerErrorCc = 'ServerError'; 2315 } 2316 } 2317 2318 # check for duplicated entries 2319 if ( defined $AddressesList{$CustomerElementCc} && $CustomerErrorCc eq '' ) { 2320 $CustomerErrorMsgCc = 'IsDuplicatedServerErrorMsg'; 2321 $CustomerErrorCc = 'ServerError'; 2322 } 2323 2324 my $CustomerDisabledCc = ''; 2325 my $CountAuxCc = $CustomerCounterCc++; 2326 if ( $CustomerErrorCc ne '' ) { 2327 $CustomerDisabledCc = 'disabled="disabled"'; 2328 $CountAuxCc = $Count . 'Error'; 2329 } 2330 2331 if ( $CustomerQueueCc ne '' ) { 2332 $CustomerQueueCc = $Count; 2333 } 2334 2335 push @MultipleCustomerCc, { 2336 Count => $CountAuxCc, 2337 CustomerElement => $CustomerElementCc, 2338 CustomerKey => $CustomerKeyCc, 2339 CustomerError => $CustomerErrorCc, 2340 CustomerErrorMsg => $CustomerErrorMsgCc, 2341 CustomerDisabled => $CustomerDisabledCc, 2342 CustomerQueue => $CustomerQueueCc, 2343 }; 2344 $AddressesList{$CustomerElementCc} = 1; 2345 } 2346 } 2347 } 2348 2349 my @MultipleCustomerBcc; 2350 my $CustomersNumberBcc = $ParamObject->GetParam( Param => 'CustomerTicketCounterBccCustomer' ) || 0; 2351 2352 if ($CustomersNumberBcc) { 2353 my $CustomerCounterBcc = 1; 2354 for my $Count ( 1 ... $CustomersNumberBcc ) { 2355 my $CustomerElementBcc = $ParamObject->GetParam( Param => 'BccCustomerTicketText_' . $Count ); 2356 my $CustomerKeyBcc = $ParamObject->GetParam( Param => 'BccCustomerKey_' . $Count ) 2357 || ''; 2358 my $CustomerQueueBcc = $ParamObject->GetParam( Param => 'BccCustomerQueue_' . $Count ) 2359 || ''; 2360 2361 if ($CustomerElementBcc) { 2362 2363 if ( $GetParam{Bcc} ) { 2364 $GetParam{Bcc} .= ', ' . $CustomerElementBcc; 2365 } 2366 else { 2367 $GetParam{Bcc} = $CustomerElementBcc; 2368 } 2369 2370 # check email address 2371 my $CustomerErrorMsgBcc = 'CustomerGenericServerErrorMsg'; 2372 my $CustomerErrorBcc = ''; 2373 for my $Email ( Mail::Address->parse($CustomerElementBcc) ) { 2374 if ( !$CheckItemObject->CheckEmail( Address => $Email->address() ) ) { 2375 $CustomerErrorMsgBcc = $CheckItemObject->CheckErrorType() 2376 . 'ServerErrorMsg'; 2377 $CustomerErrorBcc = 'ServerError'; 2378 } 2379 } 2380 2381 # check for duplicated entries 2382 if ( defined $AddressesList{$CustomerElementBcc} && $CustomerErrorBcc eq '' ) { 2383 $CustomerErrorMsgBcc = 'IsDuplicatedServerErrorMsg'; 2384 $CustomerErrorBcc = 'ServerError'; 2385 } 2386 2387 my $CustomerDisabledBcc = ''; 2388 my $CountAuxBcc = $CustomerCounterBcc++; 2389 if ( $CustomerErrorBcc ne '' ) { 2390 $CustomerDisabledBcc = 'disabled="disabled"'; 2391 $CountAuxBcc = $Count . 'Error'; 2392 } 2393 2394 if ( $CustomerQueueBcc ne '' ) { 2395 $CustomerQueueBcc = $Count; 2396 } 2397 2398 push @MultipleCustomerBcc, { 2399 Count => $CountAuxBcc, 2400 CustomerElement => $CustomerElementBcc, 2401 CustomerKey => $CustomerKeyBcc, 2402 CustomerError => $CustomerErrorBcc, 2403 CustomerErrorMsg => $CustomerErrorMsgBcc, 2404 CustomerDisabled => $CustomerDisabledBcc, 2405 CustomerQueue => $CustomerQueueBcc, 2406 }; 2407 $AddressesList{$CustomerElementBcc} = 1; 2408 } 2409 } 2410 } 2411 2412 return ( 2413 GetParam => \%GetParam, 2414 MultipleCustomer => \@MultipleCustomer, 2415 MultipleCustomerCc => \@MultipleCustomerCc, 2416 MultipleCustomerBcc => \@MultipleCustomerBcc, 2417 ); 2418} 2419 2420sub _GetStandardTemplates { 2421 my ( $Self, %Param ) = @_; 2422 2423 # get create templates 2424 my %Templates; 2425 2426 # check needed 2427 return \%Templates if !$Param{QueueID} && !$Param{TicketID}; 2428 2429 my $QueueID = $Param{QueueID} || ''; 2430 if ( !$Param{QueueID} && $Param{TicketID} ) { 2431 2432 # get QueueID from the ticket 2433 my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet( 2434 TicketID => $Param{TicketID}, 2435 DynamicFields => 0, 2436 UserID => $Self->{UserID}, 2437 ); 2438 $QueueID = $Ticket{QueueID} || ''; 2439 } 2440 2441 # fetch all std. templates 2442 my %StandardTemplates = $Kernel::OM->Get('Kernel::System::Queue')->QueueStandardTemplateMemberList( 2443 QueueID => $QueueID, 2444 TemplateTypes => 1, 2445 ); 2446 2447 # return empty hash if there are no templates for this screen 2448 return \%Templates if !IsHashRefWithData( $StandardTemplates{Email} ); 2449 2450 # return just the templates for this screen 2451 return $StandardTemplates{Email}; 2452} 2453 24541; 2455