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