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::AgentTicketActionCommon; 10 11use strict; 12use warnings; 13 14use Kernel::System::EmailParser; 15use Kernel::System::VariableCheck qw(:all); 16use Kernel::Language qw(Translatable); 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 # get article for whom this should be a reply, if available 41 my $ReplyToArticle = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'ReplyToArticle' ) || ''; 42 my $TicketID = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'TicketID' ) || ''; 43 44 # check if ReplyToArticle really belongs to the ticket 45 my %ReplyToArticleContent; 46 my @ReplyToAdresses; 47 if ($ReplyToArticle) { 48 49 my $ArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle( 50 TicketID => $TicketID, 51 ArticleID => $ReplyToArticle, 52 ); 53 %ReplyToArticleContent = $ArticleBackendObject->ArticleGet( 54 TicketID => $TicketID, 55 ArticleID => $ReplyToArticle, 56 DynamicFields => 0, 57 ); 58 59 $Self->{ReplyToArticle} = $ReplyToArticle; 60 $Self->{ReplyToArticleContent} = \%ReplyToArticleContent; 61 62 # get sender of original note (to inform sender about answer) 63 if ( $ReplyToArticleContent{CreateBy} ) { 64 my @ReplyToSenderID = ( $ReplyToArticleContent{CreateBy} ); 65 $Self->{ReplyToSenderUserID} = \@ReplyToSenderID; 66 } 67 68 # if article belongs to other ticket, don't use it as reply 69 if ( $ReplyToArticleContent{TicketID} ne $Self->{TicketID} ) { 70 $Self->{ReplyToArticle} = ""; 71 } 72 73 # if article is not of type note-internal, don't use it as reply 74 if ( 75 $ArticleBackendObject->ChannelNameGet() ne 'Internal' 76 || ( 77 $ArticleBackendObject->ChannelNameGet() eq 'Internal' 78 && $ReplyToArticleContent{SenderType} ne 'agent' 79 ) 80 ) 81 { 82 $Self->{ReplyToArticle} = ""; 83 } 84 } 85 86 # get form id 87 $Self->{FormID} = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'FormID' ); 88 89 # create form id 90 if ( !$Self->{FormID} ) { 91 $Self->{FormID} = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDCreate(); 92 } 93 94 return $Self; 95} 96 97sub Run { 98 my ( $Self, %Param ) = @_; 99 100 # get needed objects 101 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 102 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 103 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 104 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 105 106 # check needed stuff 107 if ( !$Self->{TicketID} ) { 108 return $LayoutObject->ErrorScreen( 109 Message => Translatable('No TicketID is given!'), 110 Comment => Translatable('Please contact the administrator.'), 111 ); 112 } 113 114 # get config of frontend module 115 my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}"); 116 117 # check permissions 118 my $Access = $TicketObject->TicketPermission( 119 Type => $Config->{Permission}, 120 TicketID => $Self->{TicketID}, 121 UserID => $Self->{UserID} 122 ); 123 124 # error screen, don't show ticket 125 if ( !$Access ) { 126 return $LayoutObject->NoPermission( 127 Message => $LayoutObject->{LanguageObject}->Translate( 'You need %s permissions!', $Config->{Permission} ), 128 WithHeader => 'yes', 129 ); 130 } 131 132 # get ACL restrictions 133 my %PossibleActions = ( 1 => $Self->{Action} ); 134 135 my $ACL = $TicketObject->TicketAcl( 136 Data => \%PossibleActions, 137 Action => $Self->{Action}, 138 TicketID => $Self->{TicketID}, 139 ReturnType => 'Action', 140 ReturnSubType => '-', 141 UserID => $Self->{UserID}, 142 ); 143 my %AclAction = $TicketObject->TicketAclActionData(); 144 145 # check if ACL restrictions exist 146 if ( $ACL || IsHashRefWithData( \%AclAction ) ) { 147 148 my %AclActionLookup = reverse %AclAction; 149 150 # show error screen if ACL prohibits this action 151 if ( !$AclActionLookup{ $Self->{Action} } ) { 152 return $LayoutObject->NoPermission( WithHeader => 'yes' ); 153 } 154 } 155 156 # Check for failed draft loading request. 157 if ( 158 $ParamObject->GetParam( Param => 'LoadFormDraft' ) 159 && !$Self->{LoadedFormDraftID} 160 ) 161 { 162 return $LayoutObject->ErrorScreen( 163 Message => Translatable('Loading draft failed!'), 164 Comment => Translatable('Please contact the administrator.'), 165 ); 166 } 167 168 my %Ticket = $TicketObject->TicketGet( 169 TicketID => $Self->{TicketID}, 170 DynamicFields => 1, 171 ); 172 173 my $LoadedFormDraft; 174 if ( $Self->{LoadedFormDraftID} ) { 175 $LoadedFormDraft = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftGet( 176 FormDraftID => $Self->{LoadedFormDraftID}, 177 GetContent => 0, 178 UserID => $Self->{UserID}, 179 ); 180 181 my @Articles = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleList( 182 TicketID => $Self->{TicketID}, 183 OnlyLast => 1, 184 ); 185 186 if (@Articles) { 187 my $LastArticle = $Articles[0]; 188 189 my $LastArticleSystemTime; 190 if ( $LastArticle->{CreateTime} ) { 191 my $LastArticleSystemTimeObject = $Kernel::OM->Create( 192 'Kernel::System::DateTime', 193 ObjectParams => { 194 String => $LastArticle->{CreateTime}, 195 }, 196 ); 197 $LastArticleSystemTime = $LastArticleSystemTimeObject->ToEpoch(); 198 } 199 200 my $FormDraftSystemTimeObject = $Kernel::OM->Create( 201 'Kernel::System::DateTime', 202 ObjectParams => { 203 String => $LoadedFormDraft->{ChangeTime}, 204 }, 205 ); 206 my $FormDraftSystemTime = $FormDraftSystemTimeObject->ToEpoch(); 207 208 if ( !$LastArticleSystemTime || $FormDraftSystemTime <= $LastArticleSystemTime ) { 209 $Param{FormDraftOutdated} = 1; 210 } 211 } 212 } 213 214 if ( IsHashRefWithData($LoadedFormDraft) ) { 215 216 $LoadedFormDraft->{ChangeByName} = $Kernel::OM->Get('Kernel::System::User')->UserName( 217 UserID => $LoadedFormDraft->{ChangeBy}, 218 ); 219 } 220 221 $LayoutObject->Block( 222 Name => 'Properties', 223 Data => { 224 FormDraft => $Config->{FormDraft}, 225 FormDraftID => $Self->{LoadedFormDraftID}, 226 FormDraftTitle => $LoadedFormDraft ? $LoadedFormDraft->{Title} : '', 227 FormDraftMeta => $LoadedFormDraft, 228 FormID => $Self->{FormID}, 229 ReplyToArticle => $Self->{ReplyToArticle}, 230 %Ticket, 231 %Param, 232 }, 233 ); 234 235 # show right header 236 $LayoutObject->Block( 237 Name => 'Header' . $Self->{Action}, 238 Data => { 239 %Ticket, 240 }, 241 ); 242 243 # get lock state 244 if ( $Config->{RequiredLock} ) { 245 if ( !$TicketObject->TicketLockGet( TicketID => $Self->{TicketID} ) ) { 246 247 my $Lock = $TicketObject->TicketLockSet( 248 TicketID => $Self->{TicketID}, 249 Lock => 'lock', 250 UserID => $Self->{UserID} 251 ); 252 253 if ($Lock) { 254 255 # Set new owner if ticket owner is different then logged user. 256 if ( $Ticket{OwnerID} != $Self->{UserID} ) { 257 258 # Remember previous owner, which will be used to restore ticket owner on undo action. 259 $Param{PreviousOwner} = $Ticket{OwnerID}; 260 261 $TicketObject->TicketOwnerSet( 262 TicketID => $Self->{TicketID}, 263 UserID => $Self->{UserID}, 264 NewUserID => $Self->{UserID}, 265 ); 266 } 267 268 # Show lock state. 269 $LayoutObject->Block( 270 Name => 'PropertiesLock', 271 Data => { 272 %Param, 273 TicketID => $Self->{TicketID}, 274 }, 275 ); 276 } 277 } 278 else { 279 my $AccessOk = $TicketObject->OwnerCheck( 280 TicketID => $Self->{TicketID}, 281 OwnerID => $Self->{UserID}, 282 ); 283 if ( !$AccessOk ) { 284 my $Output = $LayoutObject->Header( 285 Type => 'Small', 286 Value => $Ticket{Number}, 287 BodyClass => 'Popup', 288 ); 289 $Output .= $LayoutObject->Warning( 290 Message => Translatable('Sorry, you need to be the ticket owner to perform this action.'), 291 Comment => Translatable('Please change the owner first.'), 292 ); 293 $Output .= $LayoutObject->Footer( 294 Type => 'Small', 295 ); 296 return $Output; 297 } 298 299 # show back link 300 $LayoutObject->Block( 301 Name => 'TicketBack', 302 Data => { 303 %Param, 304 TicketID => $Self->{TicketID}, 305 }, 306 ); 307 } 308 } 309 else { 310 $LayoutObject->Block( 311 Name => 'TicketBack', 312 Data => { 313 %Param, 314 %Ticket, 315 }, 316 ); 317 } 318 319 # get params 320 my %GetParam; 321 for my $Key ( 322 qw( 323 NewStateID NewPriorityID TimeUnits IsVisibleForCustomer Title Body Subject NewQueueID 324 Year Month Day Hour Minute NewOwnerID NewResponsibleID TypeID ServiceID SLAID 325 Expand ReplyToArticle StandardTemplateID CreateArticle FormDraftID Title 326 ) 327 ) 328 { 329 $GetParam{$Key} = $ParamObject->GetParam( Param => $Key ); 330 } 331 332 # ACL compatibility translation 333 my %ACLCompatGetParam = ( 334 StateID => $GetParam{NewStateID}, 335 PriorityID => $GetParam{NewPriorityID}, 336 QueueID => $GetParam{NewQueueID}, 337 OwnerID => $GetParam{NewOwnerID}, 338 ResponsibleID => $GetParam{NewResponsibleID}, 339 ); 340 341 # get dynamic field values form http request 342 my %DynamicFieldValues; 343 344 # define the dynamic fields to show based on the object type 345 my $ObjectType = ['Ticket']; 346 347 # only screens that add notes can modify Article dynamic fields 348 if ( $Config->{Note} ) { 349 $ObjectType = [ 'Ticket', 'Article' ]; 350 } 351 352 # get the dynamic fields for this screen 353 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 354 Valid => 1, 355 ObjectType => $ObjectType, 356 FieldFilter => $Config->{DynamicField} || {}, 357 ); 358 359 # get dynamic field backend object 360 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 361 362 # cycle trough the activated Dynamic Fields for this screen 363 DYNAMICFIELD: 364 for my $DynamicFieldConfig ( @{$DynamicField} ) { 365 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 366 367 # extract the dynamic field value from the web request 368 $DynamicFieldValues{ $DynamicFieldConfig->{Name} } = $DynamicFieldBackendObject->EditFieldValueGet( 369 DynamicFieldConfig => $DynamicFieldConfig, 370 ParamObject => $ParamObject, 371 LayoutObject => $LayoutObject, 372 ); 373 } 374 375 # convert dynamic field values into a structure for ACLs 376 my %DynamicFieldACLParameters; 377 DYNAMICFIELD: 378 for my $DynamicFieldItem ( sort keys %DynamicFieldValues ) { 379 next DYNAMICFIELD if !$DynamicFieldItem; 380 next DYNAMICFIELD if !defined $DynamicFieldValues{$DynamicFieldItem}; 381 382 $DynamicFieldACLParameters{ 'DynamicField_' . $DynamicFieldItem } = $DynamicFieldValues{$DynamicFieldItem}; 383 } 384 $GetParam{DynamicField} = \%DynamicFieldACLParameters; 385 386 # transform pending time, time stamp based on user time zone 387 if ( 388 defined $GetParam{Year} 389 && defined $GetParam{Month} 390 && defined $GetParam{Day} 391 && defined $GetParam{Hour} 392 && defined $GetParam{Minute} 393 ) 394 { 395 %GetParam = $LayoutObject->TransformDateSelection( 396 %GetParam, 397 ); 398 } 399 400 # rewrap body if no rich text is used 401 if ( $GetParam{Body} && !$LayoutObject->{BrowserRichText} ) { 402 $GetParam{Body} = $LayoutObject->WrapPlainText( 403 MaxCharacters => $ConfigObject->Get('Ticket::Frontend::TextAreaNote'), 404 PlainText => $GetParam{Body}, 405 ); 406 } 407 408 # get upload cache object 409 my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache'); 410 411 if ( 412 $Self->{Subaction} eq 'Store' 413 || $Self->{LoadedFormDraftID} 414 ) 415 { 416 417 # challenge token check for write action 418 if ( $Self->{Subaction} eq 'Store' ) { 419 $LayoutObject->ChallengeTokenCheck(); 420 } 421 422 $GetParam{IsVisibleForCustomer} //= 0; 423 424 # store action 425 my %Error; 426 427 # get all attachments meta data 428 my @Attachments = $UploadCacheObject->FormIDGetAllFilesMeta( 429 FormID => $Self->{FormID}, 430 ); 431 432 # Get and validate draft action. 433 my $FormDraftAction = $ParamObject->GetParam( Param => 'FormDraftAction' ); 434 if ( $FormDraftAction && !$Config->{FormDraft} ) { 435 return $LayoutObject->ErrorScreen( 436 Message => Translatable('FormDraft functionality disabled!'), 437 Comment => Translatable('Please contact the administrator.'), 438 ); 439 } 440 441 my %FormDraftResponse; 442 443 # Check draft name. 444 if ( 445 $FormDraftAction 446 && ( $FormDraftAction eq 'Add' || $FormDraftAction eq 'Update' ) 447 ) 448 { 449 my $Title = $ParamObject->GetParam( Param => 'FormDraftTitle' ); 450 451 # A draft name is required. 452 if ( !$Title ) { 453 454 %FormDraftResponse = ( 455 Success => 0, 456 ErrorMessage => $Kernel::OM->Get('Kernel::Language')->Translate("Draft name is required!"), 457 ); 458 } 459 460 # Chosen draft name must be unique. 461 else { 462 my $FormDraftList = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftListGet( 463 ObjectType => 'Ticket', 464 ObjectID => $Self->{TicketID}, 465 Action => $Self->{Action}, 466 UserID => $Self->{UserID}, 467 ); 468 DRAFT: 469 for my $FormDraft ( @{$FormDraftList} ) { 470 471 # No existing draft with same name. 472 next DRAFT if $Title ne $FormDraft->{Title}; 473 474 # Same name for update on existing draft. 475 if ( 476 $GetParam{FormDraftID} 477 && $FormDraftAction eq 'Update' 478 && $GetParam{FormDraftID} eq $FormDraft->{FormDraftID} 479 ) 480 { 481 next DRAFT; 482 } 483 484 # Another draft with the chosen name already exists. 485 %FormDraftResponse = ( 486 Success => 0, 487 ErrorMessage => $Kernel::OM->Get('Kernel::Language') 488 ->Translate( "FormDraft name %s is already in use!", $Title ), 489 ); 490 last DRAFT; 491 } 492 } 493 } 494 495 # Perform draft action instead of saving form data in ticket/article. 496 if ( $FormDraftAction && !%FormDraftResponse ) { 497 498 # Reset FormDraftID to prevent updating existing draft. 499 if ( $FormDraftAction eq 'Add' && $GetParam{FormDraftID} ) { 500 $ParamObject->{Query}->param( 501 -name => 'FormDraftID', 502 -value => '', 503 ); 504 } 505 506 my $FormDraftActionOk; 507 if ( 508 $FormDraftAction eq 'Add' 509 || 510 ( $FormDraftAction eq 'Update' && $GetParam{FormDraftID} ) 511 ) 512 { 513 $FormDraftActionOk = $ParamObject->SaveFormDraft( 514 UserID => $Self->{UserID}, 515 ObjectType => 'Ticket', 516 ObjectID => $Self->{TicketID}, 517 OverrideParams => { 518 ReplyToArticle => undef, 519 }, 520 ); 521 } 522 523 if ($FormDraftActionOk) { 524 $FormDraftResponse{Success} = 1; 525 } 526 else { 527 %FormDraftResponse = ( 528 Success => 0, 529 ErrorMessage => 'Could not perform requested draft action!', 530 ); 531 } 532 } 533 534 if (%FormDraftResponse) { 535 536 # build JSON output 537 my $JSON = $LayoutObject->JSONEncode( 538 Data => \%FormDraftResponse, 539 ); 540 541 # send JSON response 542 return $LayoutObject->Attachment( 543 ContentType => 'application/json; charset=' . $LayoutObject->{Charset}, 544 Content => $JSON, 545 Type => 'inline', 546 NoCache => 1, 547 ); 548 } 549 550 # get state object 551 my $StateObject = $Kernel::OM->Get('Kernel::System::State'); 552 553 # check pending time 554 if ( $GetParam{NewStateID} ) { 555 my %StateData = $StateObject->StateGet( 556 ID => $GetParam{NewStateID}, 557 ); 558 559 # check state type 560 if ( $StateData{TypeName} =~ /^pending/i ) { 561 562 # check needed stuff 563 for my $Needed (qw(Year Month Day Hour Minute)) { 564 if ( !defined $GetParam{$Needed} ) { 565 $Error{'DateInvalid'} = 'ServerError'; 566 } 567 } 568 569 # create datetime object 570 my $PendingDateTimeObject = $Kernel::OM->Create( 571 'Kernel::System::DateTime', 572 ObjectParams => { 573 %GetParam, 574 Second => 0, 575 }, 576 ); 577 578 # get current system epoch 579 my $CurSystemDateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 580 581 # check date 582 if ( 583 !$PendingDateTimeObject 584 || $PendingDateTimeObject < $CurSystemDateTimeObject 585 ) 586 { 587 $Error{'DateInvalid'} = 'ServerError'; 588 } 589 } 590 } 591 592 if ( $Config->{Note} && $Config->{NoteMandatory} ) { 593 594 # check subject 595 if ( !$GetParam{Subject} ) { 596 $Error{'SubjectInvalid'} = 'ServerError'; 597 } 598 599 # check body 600 if ( !$GetParam{Body} ) { 601 $Error{'BodyInvalid'} = 'ServerError'; 602 } 603 } 604 605 # check owner 606 if ( $Config->{Owner} && $Config->{OwnerMandatory} ) { 607 if ( !$GetParam{NewOwnerID} ) { 608 $Error{'NewOwnerInvalid'} = 'ServerError'; 609 } 610 } 611 612 # check responsible 613 if ( $Config->{Responsible} && $Config->{ResponsibleMandatory} ) { 614 if ( !$GetParam{NewResponsibleID} ) { 615 $Error{'NewResponsibleInvalid'} = 'ServerError'; 616 } 617 } 618 619 # check title 620 if ( $Config->{Title} && !$GetParam{Title} ) { 621 $Error{'TitleInvalid'} = 'ServerError'; 622 } 623 624 # check type 625 if ( 626 ( $ConfigObject->Get('Ticket::Type') ) 627 && 628 ( $Config->{TicketType} ) && 629 ( !$GetParam{TypeID} ) 630 ) 631 { 632 $Error{'TypeIDInvalid'} = ' ServerError'; 633 } 634 635 # check service 636 if ( 637 $ConfigObject->Get('Ticket::Service') 638 && $Config->{Service} 639 && $GetParam{SLAID} 640 && !$GetParam{ServiceID} 641 ) 642 { 643 $Error{'ServiceInvalid'} = ' ServerError'; 644 } 645 646 # check mandatory service 647 if ( 648 $ConfigObject->Get('Ticket::Service') 649 && $Config->{Service} 650 && $Config->{ServiceMandatory} 651 && !$GetParam{ServiceID} 652 ) 653 { 654 $Error{'ServiceInvalid'} = ' ServerError'; 655 } 656 657 # check mandatory sla 658 if ( 659 $ConfigObject->Get('Ticket::Service') 660 && $Config->{Service} 661 && $Config->{SLAMandatory} 662 && !$GetParam{SLAID} 663 ) 664 { 665 $Error{'SLAInvalid'} = ' ServerError'; 666 } 667 668 # check mandatory queue 669 if ( $Config->{Queue} && $Config->{QueueMandatory} ) { 670 if ( !$GetParam{NewQueueID} ) { 671 $Error{'NewQueueInvalid'} = 'ServerError'; 672 } 673 } 674 675 # check mandatory state 676 if ( $Config->{State} && $Config->{StateMandatory} ) { 677 if ( !$GetParam{NewStateID} ) { 678 $Error{'NewStateInvalid'} = 'ServerError'; 679 } 680 } 681 682 # check time units, but only if the current screen has a note 683 # (accounted time can only be stored if and article is generated) 684 if ( 685 $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 686 && $Config->{Note} 687 && $GetParam{TimeUnits} eq '' 688 ) 689 { 690 $Error{'TimeUnitsInvalid'} = ' ServerError'; 691 } 692 693 # check expand 694 if ( $GetParam{Expand} ) { 695 %Error = (); 696 $Error{Expand} = 1; 697 } 698 699 # create html strings for all dynamic fields 700 my @TicketTypeDynamicFields; 701 my @ArticleTypeDynamicFields; 702 703 # cycle trough the activated Dynamic Fields for this screen 704 DYNAMICFIELD: 705 for my $DynamicFieldConfig ( @{$DynamicField} ) { 706 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 707 708 my $PossibleValuesFilter; 709 710 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 711 DynamicFieldConfig => $DynamicFieldConfig, 712 Behavior => 'IsACLReducible', 713 ); 714 715 if ($IsACLReducible) { 716 717 # get PossibleValues 718 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 719 DynamicFieldConfig => $DynamicFieldConfig, 720 ); 721 722 # check if field has PossibleValues property in its configuration 723 if ( IsHashRefWithData($PossibleValues) ) { 724 725 # convert possible values key => value to key => key for ACLs using a Hash slice 726 my %AclData = %{$PossibleValues}; 727 @AclData{ keys %AclData } = keys %AclData; 728 729 # set possible values filter from ACLs 730 my $ACL = $TicketObject->TicketAcl( 731 %GetParam, 732 Action => $Self->{Action}, 733 TicketID => $Self->{TicketID}, 734 ReturnType => 'Ticket', 735 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 736 Data => \%AclData, 737 UserID => $Self->{UserID}, 738 ); 739 if ($ACL) { 740 my %Filter = $TicketObject->TicketAclData(); 741 742 # convert Filer key => key back to key => value using map 743 %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} } 744 keys %Filter; 745 } 746 } 747 } 748 749 my $ValidationResult; 750 751 # Do not validate only if object type is Article and CreateArticle value is not defined. 752 if ( !( $DynamicFieldConfig->{ObjectType} eq 'Article' && !$GetParam{CreateArticle} ) ) { 753 754 $ValidationResult = $DynamicFieldBackendObject->EditFieldValueValidate( 755 DynamicFieldConfig => $DynamicFieldConfig, 756 PossibleValuesFilter => $PossibleValuesFilter, 757 ParamObject => $ParamObject, 758 Mandatory => 759 $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2, 760 ); 761 762 if ( !IsHashRefWithData($ValidationResult) ) { 763 return $LayoutObject->ErrorScreen( 764 Message => 765 $LayoutObject->{LanguageObject}->Translate( 766 'Could not perform validation on field %s!', $DynamicFieldConfig->{Label} 767 ), 768 Comment => Translatable('Please contact the administrator.'), 769 ); 770 } 771 772 # Propagate validation error to the Error variable to be detected by the frontend. 773 if ( $ValidationResult->{ServerError} ) 774 { 775 $Error{ $DynamicFieldConfig->{Name} } = ' ServerError'; 776 } 777 } 778 779 if ( $DynamicFieldConfig->{ObjectType} eq 'Ticket' ) { 780 781 # Get field html. 782 my $DynamicFieldHTML = $DynamicFieldBackendObject->EditFieldRender( 783 DynamicFieldConfig => $DynamicFieldConfig, 784 PossibleValuesFilter => $PossibleValuesFilter, 785 ServerError => $ValidationResult->{ServerError} || '', 786 ErrorMessage => $ValidationResult->{ErrorMessage} || '', 787 Mandatory => $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2, 788 LayoutObject => $LayoutObject, 789 ParamObject => $ParamObject, 790 AJAXUpdate => 1, 791 UpdatableFields => $Self->_GetFieldsToUpdate(), 792 ); 793 794 push @TicketTypeDynamicFields, { 795 Name => $DynamicFieldConfig->{Name}, 796 Label => $DynamicFieldHTML->{Label}, 797 Field => $DynamicFieldHTML->{Field}, 798 }; 799 } 800 elsif ( $DynamicFieldConfig->{ObjectType} eq 'Article' ) { 801 my $Class = ''; 802 my $MandatoryTooltip = 0; 803 804 if ( $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2 ) { 805 if ( 806 $Config->{NoteMandatory} || 807 $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 808 ) 809 { 810 $Class = 'Validate_Required'; 811 } 812 else { 813 $Class = 'Validate_DependingRequiredAND Validate_Depending_CreateArticle'; 814 $MandatoryTooltip = 1; 815 } 816 } 817 818 # Get field html. 819 my $DynamicFieldHTML = $DynamicFieldBackendObject->EditFieldRender( 820 DynamicFieldConfig => $DynamicFieldConfig, 821 PossibleValuesFilter => $PossibleValuesFilter, 822 ServerError => $ValidationResult->{ServerError} || '', 823 ErrorMessage => $ValidationResult->{ErrorMessage} || '', 824 Mandatory => ( $Class eq 'Validate_Required' ) ? 1 : 0, 825 Class => $Class, 826 LayoutObject => $LayoutObject, 827 ParamObject => $ParamObject, 828 AJAXUpdate => 1, 829 UpdatableFields => $Self->_GetFieldsToUpdate(), 830 ); 831 832 push @ArticleTypeDynamicFields, { 833 Name => $DynamicFieldConfig->{Name}, 834 Label => $DynamicFieldHTML->{Label}, 835 Field => $DynamicFieldHTML->{Field}, 836 MandatoryTooltip => $MandatoryTooltip, 837 }; 838 } 839 } 840 841 # Make sure we don't save form if a draft was loaded. 842 if ( $Self->{LoadedFormDraftID} ) { 843 %Error = ( LoadedFormDraft => 1 ); 844 } 845 846 # check errors 847 if (%Error) { 848 849 my $Output = $LayoutObject->Header( 850 Type => 'Small', 851 Value => $Ticket{TicketNumber}, 852 BodyClass => 'Popup', 853 ); 854 $Output .= $Self->_Mask( 855 Attachments => \@Attachments, 856 TimeUnitsRequired => ( 857 $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 858 ? 'Validate_Required' 859 : '' 860 ), 861 %Ticket, 862 TicketTypeDynamicFields => \@TicketTypeDynamicFields, 863 ArticleTypeDynamicFields => \@ArticleTypeDynamicFields, 864 865 %GetParam, 866 %Error, 867 ); 868 $Output .= $LayoutObject->Footer( 869 Type => 'Small', 870 ); 871 return $Output; 872 } 873 874 # set new title 875 if ( $Config->{Title} ) { 876 if ( defined $GetParam{Title} ) { 877 $TicketObject->TicketTitleUpdate( 878 Title => $GetParam{Title}, 879 TicketID => $Self->{TicketID}, 880 UserID => $Self->{UserID}, 881 ); 882 } 883 } 884 885 # set new type 886 if ( $ConfigObject->Get('Ticket::Type') && $Config->{TicketType} ) { 887 if ( $GetParam{TypeID} ) { 888 $TicketObject->TicketTypeSet( 889 Action => $Self->{Action}, 890 TypeID => $GetParam{TypeID}, 891 TicketID => $Self->{TicketID}, 892 UserID => $Self->{UserID}, 893 ); 894 } 895 } 896 897 # set new service 898 if ( $ConfigObject->Get('Ticket::Service') && $Config->{Service} ) { 899 if ( defined $GetParam{ServiceID} ) { 900 $TicketObject->TicketServiceSet( 901 %GetParam, 902 %ACLCompatGetParam, 903 Action => $Self->{Action}, 904 ServiceID => $GetParam{ServiceID}, 905 TicketID => $Self->{TicketID}, 906 CustomerUserID => $Ticket{CustomerUserID}, 907 UserID => $Self->{UserID}, 908 ); 909 } 910 if ( defined $GetParam{SLAID} ) { 911 $TicketObject->TicketSLASet( 912 Action => $Self->{Action}, 913 SLAID => $GetParam{SLAID}, 914 TicketID => $Self->{TicketID}, 915 UserID => $Self->{UserID}, 916 ); 917 } 918 } 919 920 my $UnlockOnAway = 1; 921 922 # move ticket to a new queue, but only if the queue was changed 923 if ( 924 $Config->{Queue} 925 && $GetParam{NewQueueID} 926 && $GetParam{NewQueueID} ne $Ticket{QueueID} 927 ) 928 { 929 930 # move ticket (send notification if no new owner is selected) 931 my $BodyAsText = ''; 932 if ( $LayoutObject->{BrowserRichText} ) { 933 $BodyAsText = $LayoutObject->RichText2Ascii( 934 String => $GetParam{Body} || 0, 935 ); 936 } 937 else { 938 $BodyAsText = $GetParam{Body} || 0; 939 } 940 my $Move = $TicketObject->TicketQueueSet( 941 QueueID => $GetParam{NewQueueID}, 942 UserID => $Self->{UserID}, 943 TicketID => $Self->{TicketID}, 944 SendNoNotification => $GetParam{NewUserID}, 945 Comment => $BodyAsText, 946 Action => $Self->{Action}, 947 ); 948 if ( !$Move ) { 949 return $LayoutObject->ErrorScreen(); 950 } 951 } 952 953 # set new owner 954 my @NotifyDone; 955 if ( $Config->{Owner} ) { 956 my $BodyText = $LayoutObject->RichText2Ascii( 957 String => $GetParam{Body} || '', 958 ); 959 if ( $GetParam{NewOwnerID} ) { 960 $TicketObject->TicketLockSet( 961 TicketID => $Self->{TicketID}, 962 Lock => 'lock', 963 UserID => $Self->{UserID}, 964 ); 965 my $Success = $TicketObject->TicketOwnerSet( 966 TicketID => $Self->{TicketID}, 967 UserID => $Self->{UserID}, 968 NewUserID => $GetParam{NewOwnerID}, 969 Comment => $BodyText, 970 ); 971 $UnlockOnAway = 0; 972 973 # remember to not notify owner twice 974 if ( $Success && $Success eq 1 ) { 975 push @NotifyDone, $GetParam{NewOwnerID}; 976 } 977 } 978 } 979 980 # set new responsible 981 if ( $ConfigObject->Get('Ticket::Responsible') && $Config->{Responsible} ) { 982 if ( $GetParam{NewResponsibleID} ) { 983 my $BodyText = $LayoutObject->RichText2Ascii( 984 String => $GetParam{Body} || '', 985 ); 986 my $Success = $TicketObject->TicketResponsibleSet( 987 TicketID => $Self->{TicketID}, 988 UserID => $Self->{UserID}, 989 NewUserID => $GetParam{NewResponsibleID}, 990 Comment => $BodyText, 991 ); 992 993 # remember to not notify responsible twice 994 if ( $Success && $Success eq 1 ) { 995 push @NotifyDone, $GetParam{NewResponsibleID}; 996 } 997 } 998 } 999 1000 # add note 1001 my $ArticleID = ''; 1002 my $ReturnURL; 1003 1004 # set priority 1005 if ( $Config->{Priority} && $GetParam{NewPriorityID} ) { 1006 $TicketObject->TicketPrioritySet( 1007 TicketID => $Self->{TicketID}, 1008 PriorityID => $GetParam{NewPriorityID}, 1009 UserID => $Self->{UserID}, 1010 ); 1011 } 1012 1013 # set state 1014 if ( $Config->{State} && $GetParam{NewStateID} ) { 1015 $TicketObject->TicketStateSet( 1016 TicketID => $Self->{TicketID}, 1017 StateID => $GetParam{NewStateID}, 1018 UserID => $Self->{UserID}, 1019 DynamicField => $GetParam{DynamicField}, 1020 ); 1021 1022 # unlock the ticket after close 1023 my %StateData = $StateObject->StateGet( 1024 ID => $GetParam{NewStateID}, 1025 ); 1026 1027 # set unlock on close state 1028 if ( $StateData{TypeName} =~ /^close/i ) { 1029 $TicketObject->TicketLockSet( 1030 TicketID => $Self->{TicketID}, 1031 Lock => 'unlock', 1032 UserID => $Self->{UserID}, 1033 ); 1034 } 1035 1036 # set pending time on pending state 1037 elsif ( $StateData{TypeName} =~ /^pending/i ) { 1038 1039 # set pending time 1040 $TicketObject->TicketPendingTimeSet( 1041 UserID => $Self->{UserID}, 1042 TicketID => $Self->{TicketID}, 1043 %GetParam, 1044 ); 1045 } 1046 1047 # redirect parent window to last screen overview on closed tickets 1048 if ( 1049 $StateData{TypeName} =~ /^close/i 1050 && !$ConfigObject->Get('Ticket::Frontend::RedirectAfterCloseDisabled') 1051 ) 1052 { 1053 $ReturnURL = $Self->{LastScreenOverview} || 'Action=AgentDashboard'; 1054 } 1055 } 1056 1057 if ( 1058 $GetParam{CreateArticle} 1059 && $Config->{Note} 1060 && ( $GetParam{Subject} || $GetParam{Body} ) 1061 ) 1062 { 1063 1064 if ( !$GetParam{Subject} ) { 1065 if ( $Config->{Subject} ) { 1066 my $Subject = $LayoutObject->Output( 1067 Template => $Config->{Subject}, 1068 ); 1069 $GetParam{Subject} = $Subject; 1070 } 1071 $GetParam{Subject} = $GetParam{Subject} 1072 || $LayoutObject->{LanguageObject}->Translate('No subject'); 1073 } 1074 1075 # get pre loaded attachment 1076 my @Attachments = $UploadCacheObject->FormIDGetAllFilesData( 1077 FormID => $Self->{FormID}, 1078 ); 1079 1080 # get submit attachment 1081 my %UploadStuff = $ParamObject->GetUploadAll( 1082 Param => 'FileUpload', 1083 ); 1084 if (%UploadStuff) { 1085 push @Attachments, \%UploadStuff; 1086 } 1087 1088 my $MimeType = 'text/plain'; 1089 if ( $LayoutObject->{BrowserRichText} ) { 1090 $MimeType = 'text/html'; 1091 1092 # remove unused inline images 1093 my @NewAttachmentData; 1094 ATTACHMENT: 1095 for my $Attachment (@Attachments) { 1096 my $ContentID = $Attachment->{ContentID}; 1097 if ( 1098 $ContentID 1099 && ( $Attachment->{ContentType} =~ /image/i ) 1100 && ( $Attachment->{Disposition} eq 'inline' ) 1101 ) 1102 { 1103 my $ContentIDHTMLQuote = $LayoutObject->Ascii2Html( 1104 Text => $ContentID, 1105 ); 1106 1107 # workaround for link encode of rich text editor, see bug#5053 1108 my $ContentIDLinkEncode = $LayoutObject->LinkEncode($ContentID); 1109 $GetParam{Body} =~ s/(ContentID=)$ContentIDLinkEncode/$1$ContentID/g; 1110 1111 # ignore attachment if not linked in body 1112 next ATTACHMENT 1113 if $GetParam{Body} !~ /(\Q$ContentIDHTMLQuote\E|\Q$ContentID\E)/i; 1114 } 1115 1116 # remember inline images and normal attachments 1117 push @NewAttachmentData, \%{$Attachment}; 1118 } 1119 @Attachments = @NewAttachmentData; 1120 1121 # verify html document 1122 $GetParam{Body} = $LayoutObject->RichTextDocumentComplete( 1123 String => $GetParam{Body}, 1124 ); 1125 } 1126 1127 my $From = "\"$Self->{UserFullname}\" <$Self->{UserEmail}>"; 1128 my @NotifyUserIDs; 1129 1130 # get list of users that will be informed without selection in informed/involved list 1131 my @UserListWithoutSelection 1132 = split( ',', $ParamObject->GetParam( Param => 'UserListWithoutSelection' ) || "" ); 1133 1134 # get inform user list 1135 my @InformUserID = $ParamObject->GetArray( Param => 'InformUserID' ); 1136 1137 # get involved user list 1138 my @InvolvedUserID = $ParamObject->GetArray( Param => 'InvolvedUserID' ); 1139 1140 if ( $Config->{InformAgent} ) { 1141 push @NotifyUserIDs, @InformUserID; 1142 } 1143 1144 if ( $Config->{InvolvedAgent} ) { 1145 push @NotifyUserIDs, @InvolvedUserID; 1146 } 1147 1148 if ( $Self->{ReplyToArticle} ) { 1149 push @NotifyUserIDs, @UserListWithoutSelection; 1150 } 1151 1152 if ( $Self->{Action} eq 'AgentTicketEmailOutbound' ) { 1153 $ArticleID = $Kernel::OM->Get('Kernel::System::Ticket::Article::Backend::Email')->ArticleSend( 1154 TicketID => $Self->{TicketID}, 1155 SenderType => 'agent', 1156 From => $From, 1157 MimeType => $MimeType, 1158 Charset => $LayoutObject->{UserCharset}, 1159 UserID => $Self->{UserID}, 1160 HistoryType => $Config->{HistoryType}, 1161 HistoryComment => $Config->{HistoryComment}, 1162 ForceNotificationToUserID => \@NotifyUserIDs, 1163 ExcludeMuteNotificationToUserID => \@NotifyDone, 1164 UnlockOnAway => $UnlockOnAway, 1165 Attachment => \@Attachments, 1166 %GetParam, 1167 ); 1168 } 1169 else { 1170 $ArticleID = $Kernel::OM->Get('Kernel::System::Ticket::Article::Backend::Internal')->ArticleCreate( 1171 TicketID => $Self->{TicketID}, 1172 SenderType => 'agent', 1173 From => $From, 1174 MimeType => $MimeType, 1175 Charset => $LayoutObject->{UserCharset}, 1176 UserID => $Self->{UserID}, 1177 HistoryType => $Config->{HistoryType}, 1178 HistoryComment => $Config->{HistoryComment}, 1179 ForceNotificationToUserID => \@NotifyUserIDs, 1180 ExcludeMuteNotificationToUserID => \@NotifyDone, 1181 UnlockOnAway => $UnlockOnAway, 1182 Attachment => \@Attachments, 1183 %GetParam, 1184 ); 1185 } 1186 1187 if ( !$ArticleID ) { 1188 return $LayoutObject->ErrorScreen(); 1189 } 1190 1191 # time accounting 1192 if ( $GetParam{TimeUnits} ) { 1193 $TicketObject->TicketAccountTime( 1194 TicketID => $Self->{TicketID}, 1195 ArticleID => $ArticleID, 1196 TimeUnit => $GetParam{TimeUnits}, 1197 UserID => $Self->{UserID}, 1198 ); 1199 } 1200 1201 # remove pre submitted attachments 1202 $UploadCacheObject->FormIDRemove( FormID => $Self->{FormID} ); 1203 } 1204 1205 # set dynamic fields 1206 # cycle through the activated Dynamic Fields for this screen 1207 DYNAMICFIELD: 1208 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1209 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1210 1211 # set the object ID (TicketID or ArticleID) depending on the field configration 1212 my $ObjectID = $DynamicFieldConfig->{ObjectType} eq 'Article' ? $ArticleID : $Self->{TicketID}; 1213 1214 # set the value 1215 my $Success = $DynamicFieldBackendObject->ValueSet( 1216 DynamicFieldConfig => $DynamicFieldConfig, 1217 ObjectID => $ObjectID, 1218 Value => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 1219 UserID => $Self->{UserID}, 1220 ); 1221 } 1222 1223 # If form was called based on a draft, 1224 # delete draft since its content has now been used. 1225 if ( 1226 $GetParam{FormDraftID} 1227 && !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftDelete( 1228 FormDraftID => $GetParam{FormDraftID}, 1229 UserID => $Self->{UserID}, 1230 ) 1231 ) 1232 { 1233 return $LayoutObject->ErrorScreen( 1234 Message => Translatable('Could not delete draft!'), 1235 Comment => Translatable('Please contact the administrator.'), 1236 ); 1237 } 1238 1239 # load new URL in parent window and close popup 1240 $ReturnURL ||= "Action=AgentTicketZoom;TicketID=$Self->{TicketID};ArticleID=$ArticleID"; 1241 1242 return $LayoutObject->PopupClose( 1243 URL => $ReturnURL, 1244 ); 1245 } 1246 elsif ( $Self->{Subaction} eq 'AJAXUpdate' ) { 1247 my %Ticket = $TicketObject->TicketGet( TicketID => $Self->{TicketID} ); 1248 my $CustomerUser = $Ticket{CustomerUserID}; 1249 my $ElementChanged = $ParamObject->GetParam( Param => 'ElementChanged' ) || ''; 1250 1251 my $ServiceID; 1252 1253 # get service value from param if field is visible in the screen 1254 if ( $ConfigObject->Get('Ticket::Service') && $Config->{Service} ) { 1255 $ServiceID = $GetParam{ServiceID} || ''; 1256 } 1257 1258 # otherwise use ticket service value since it can't be changed 1259 elsif ( $ConfigObject->Get('Ticket::Service') ) { 1260 $ServiceID = $Ticket{ServiceID} || ''; 1261 } 1262 1263 my $QueueID = $GetParam{NewQueueID} || $Ticket{QueueID}; 1264 my $StateID = $GetParam{NewStateID} || $Ticket{StateID}; 1265 1266 # get list type 1267 my $TreeView = 0; 1268 if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) { 1269 $TreeView = 1; 1270 } 1271 1272 my $Owners = $Self->_GetOwners( 1273 %GetParam, 1274 QueueID => $QueueID, 1275 StateID => $StateID, 1276 AllUsers => $GetParam{OwnerAll}, 1277 ); 1278 my $OldOwners = $Self->_GetOldOwners( 1279 %GetParam, 1280 QueueID => $QueueID, 1281 StateID => $StateID, 1282 AllUsers => $GetParam{OwnerAll}, 1283 ); 1284 my $ResponsibleUsers = $Self->_GetResponsible( 1285 %GetParam, 1286 QueueID => $QueueID, 1287 StateID => $StateID, 1288 AllUsers => $GetParam{OwnerAll}, 1289 ); 1290 my $Priorities = $Self->_GetPriorities( 1291 %GetParam, 1292 ); 1293 my $Services = $Self->_GetServices( 1294 %GetParam, 1295 CustomerUserID => $CustomerUser, 1296 QueueID => $QueueID, 1297 StateID => $StateID, 1298 ); 1299 my $Types = $Self->_GetTypes( 1300 %GetParam, 1301 CustomerUserID => $CustomerUser, 1302 QueueID => $QueueID, 1303 StateID => $StateID, 1304 ); 1305 my $NewQueues = $Self->_GetQueues( 1306 %GetParam, 1307 ); 1308 1309 # reset previous ServiceID to reset SLA-List if no service is selected 1310 if ( !defined $ServiceID || !$Services->{$ServiceID} ) { 1311 $ServiceID = ''; 1312 } 1313 my $SLAs = $Self->_GetSLAs( 1314 %GetParam, 1315 CustomerUserID => $CustomerUser, 1316 QueueID => $QueueID, 1317 StateID => $StateID, 1318 ServiceID => $ServiceID, 1319 ); 1320 my $NextStates = $Self->_GetNextStates( 1321 %GetParam, 1322 CustomerUserID => $CustomerUser || '', 1323 QueueID => $QueueID, 1324 StateID => $StateID, 1325 ); 1326 1327 # update Dynamic Fields Possible Values via AJAX 1328 my @DynamicFieldAJAX; 1329 1330 # cycle trough the activated Dynamic Fields for this screen 1331 DYNAMICFIELD: 1332 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1333 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1334 1335 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 1336 DynamicFieldConfig => $DynamicFieldConfig, 1337 Behavior => 'IsACLReducible', 1338 ); 1339 next DYNAMICFIELD if !$IsACLReducible; 1340 1341 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 1342 DynamicFieldConfig => $DynamicFieldConfig, 1343 ); 1344 1345 # convert possible values key => value to key => key for ACLs using a Hash slice 1346 my %AclData = %{$PossibleValues}; 1347 @AclData{ keys %AclData } = keys %AclData; 1348 1349 # set possible values filter from ACLs 1350 my $ACL = $TicketObject->TicketAcl( 1351 %GetParam, 1352 Action => $Self->{Action}, 1353 TicketID => $Self->{TicketID}, 1354 QueueID => $QueueID, 1355 ReturnType => 'Ticket', 1356 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 1357 Data => \%AclData, 1358 UserID => $Self->{UserID}, 1359 ); 1360 if ($ACL) { 1361 my %Filter = $TicketObject->TicketAclData(); 1362 1363 # convert Filer key => key back to key => value using map 1364 %{$PossibleValues} = map { $_ => $PossibleValues->{$_} } keys %Filter; 1365 } 1366 1367 my $DataValues = $DynamicFieldBackendObject->BuildSelectionDataGet( 1368 DynamicFieldConfig => $DynamicFieldConfig, 1369 PossibleValues => $PossibleValues, 1370 Value => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 1371 ) || $PossibleValues; 1372 1373 # add dynamic field to the list of fields to update 1374 push( 1375 @DynamicFieldAJAX, 1376 { 1377 Name => 'DynamicField_' . $DynamicFieldConfig->{Name}, 1378 Data => $DataValues, 1379 SelectedID => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 1380 Translation => $DynamicFieldConfig->{Config}->{TranslatableValues} || 0, 1381 Max => 100, 1382 } 1383 ); 1384 } 1385 1386 my $StandardTemplates = $Self->_GetStandardTemplates( 1387 %GetParam, 1388 QueueID => $QueueID || '', 1389 ); 1390 1391 my @TemplateAJAX; 1392 1393 # update ticket body and attachements if needed. 1394 if ( $ElementChanged eq 'StandardTemplateID' ) { 1395 my @TicketAttachments; 1396 my $TemplateText; 1397 1398 # remove all attachments from the Upload cache 1399 my $RemoveSuccess = $UploadCacheObject->FormIDRemove( 1400 FormID => $Self->{FormID}, 1401 ); 1402 if ( !$RemoveSuccess ) { 1403 $Kernel::OM->Get('Kernel::System::Log')->Log( 1404 Priority => 'error', 1405 Message => "Form attachments could not be deleted!", 1406 ); 1407 } 1408 1409 # get the template text and set new attachments if a template is selected 1410 if ( IsPositiveInteger( $GetParam{StandardTemplateID} ) ) { 1411 my $TemplateGenerator = $Kernel::OM->Get('Kernel::System::TemplateGenerator'); 1412 1413 # set template text, replace smart tags (limited as ticket is not created) 1414 $TemplateText = $TemplateGenerator->Template( 1415 TemplateID => $GetParam{StandardTemplateID}, 1416 TicketID => $Self->{TicketID}, 1417 UserID => $Self->{UserID}, 1418 ); 1419 1420 # if ReplyToArticle is given, get this article to generate 1421 # the quoted article content 1422 if ( $Self->{ReplyToArticle} ) { 1423 1424 # get article to quote 1425 my $Body = $LayoutObject->ArticleQuote( 1426 TicketID => $Self->{TicketID}, 1427 ArticleID => $Self->{ReplyToArticle}, 1428 FormID => $Self->{FormID}, 1429 UploadCacheObject => $UploadCacheObject, 1430 ); 1431 1432 # prepare quoted body content 1433 $Body = $Self->_GetQuotedReplyBody( 1434 %{ $Self->{ReplyToArticleContent} }, 1435 Body => $Body, 1436 ); 1437 1438 if ( $LayoutObject->{BrowserRichText} ) { 1439 $TemplateText = $TemplateText . '<br><br>' . $Body; 1440 } 1441 else { 1442 $TemplateText = $TemplateText . "\n\n" . $Body; 1443 } 1444 } 1445 1446 # create StdAttachmentObject 1447 my $StdAttachmentObject = $Kernel::OM->Get('Kernel::System::StdAttachment'); 1448 1449 # add std. attachments to ticket 1450 my %AllStdAttachments = $StdAttachmentObject->StdAttachmentStandardTemplateMemberList( 1451 StandardTemplateID => $GetParam{StandardTemplateID}, 1452 ); 1453 for ( sort keys %AllStdAttachments ) { 1454 my %AttachmentsData = $StdAttachmentObject->StdAttachmentGet( ID => $_ ); 1455 $UploadCacheObject->FormIDAddFile( 1456 FormID => $Self->{FormID}, 1457 Disposition => 'attachment', 1458 %AttachmentsData, 1459 ); 1460 } 1461 1462 # send a list of attachments in the upload cache back to the clientside JavaScript 1463 # which renders then the list of currently uploaded attachments 1464 @TicketAttachments = $UploadCacheObject->FormIDGetAllFilesMeta( 1465 FormID => $Self->{FormID}, 1466 ); 1467 1468 for my $Attachment (@TicketAttachments) { 1469 $Attachment->{Filesize} = $LayoutObject->HumanReadableDataSize( 1470 Size => $Attachment->{Filesize}, 1471 ); 1472 } 1473 } 1474 1475 @TemplateAJAX = ( 1476 { 1477 Name => 'UseTemplateNote', 1478 Data => '0', 1479 }, 1480 { 1481 Name => 'RichText', 1482 Data => $TemplateText || '', 1483 }, 1484 { 1485 Name => 'TicketAttachments', 1486 Data => \@TicketAttachments, 1487 KeepData => 1, 1488 }, 1489 ); 1490 } 1491 1492 my $JSON = $LayoutObject->BuildSelectionJSON( 1493 [ 1494 1495 { 1496 Name => 'NewOwnerID', 1497 Data => $Owners, 1498 SelectedID => $GetParam{NewOwnerID}, 1499 Translation => 0, 1500 PossibleNone => 1, 1501 Max => 100, 1502 }, 1503 { 1504 Name => 'NewResponsibleID', 1505 Data => $ResponsibleUsers, 1506 SelectedID => $GetParam{NewResponsibleID}, 1507 Translation => 0, 1508 PossibleNone => 1, 1509 Max => 100, 1510 }, 1511 { 1512 Name => 'NewStateID', 1513 Data => $NextStates, 1514 SelectedID => $GetParam{NewStateID}, 1515 Translation => 1, 1516 PossibleNone => $Config->{StateDefault} ? 0 : 1, 1517 Max => 100, 1518 }, 1519 { 1520 Name => 'NewPriorityID', 1521 Data => $Priorities, 1522 SelectedID => $GetParam{NewPriorityID}, 1523 PossibleNone => 0, 1524 Translation => 1, 1525 Max => 100, 1526 }, 1527 { 1528 Name => 'ServiceID', 1529 Data => $Services, 1530 SelectedID => $GetParam{ServiceID}, 1531 PossibleNone => 1, 1532 Translation => 0, 1533 TreeView => $TreeView, 1534 Max => 100, 1535 }, 1536 { 1537 Name => 'SLAID', 1538 Data => $SLAs, 1539 SelectedID => $GetParam{SLAID}, 1540 PossibleNone => 1, 1541 Translation => 0, 1542 Max => 100, 1543 }, 1544 { 1545 Name => 'StandardTemplateID', 1546 Data => $StandardTemplates, 1547 SelectedID => $GetParam{StandardTemplateID}, 1548 PossibleNone => 1, 1549 Translation => 1, 1550 Max => 100, 1551 }, 1552 { 1553 Name => 'TypeID', 1554 Data => $Types, 1555 SelectedID => $GetParam{TypeID}, 1556 PossibleNone => 1, 1557 Translation => 0, 1558 Max => 100, 1559 }, 1560 { 1561 Name => 'NewQueueID', 1562 Data => $NewQueues, 1563 SelectedID => $GetParam{NewQueueID}, 1564 PossibleNone => 1, 1565 Translation => 0, 1566 TreeView => $TreeView, 1567 Max => 100, 1568 }, 1569 @DynamicFieldAJAX, 1570 @TemplateAJAX, 1571 ], 1572 ); 1573 return $LayoutObject->Attachment( 1574 ContentType => 'application/json; charset=' . $LayoutObject->{Charset}, 1575 Content => $JSON, 1576 Type => 'inline', 1577 NoCache => 1, 1578 ); 1579 } 1580 else { 1581 1582 my $Body = ''; 1583 1584 # if ReplyToArticle is given, get this article to generate 1585 # the quoted article content 1586 if ( $Self->{ReplyToArticle} ) { 1587 1588 # get article to quote 1589 $Body = $LayoutObject->ArticleQuote( 1590 TicketID => $Self->{TicketID}, 1591 ArticleID => $Self->{ReplyToArticle}, 1592 FormID => $Self->{FormID}, 1593 UploadCacheObject => $UploadCacheObject, 1594 ); 1595 1596 # prepare quoted body content 1597 $Body = $Self->_GetQuotedReplyBody( 1598 %{ $Self->{ReplyToArticleContent} }, 1599 Body => $Body, 1600 ); 1601 } 1602 1603 # if a body content was pre defined, add this before the quoted article content 1604 if ( $GetParam{Body} ) { 1605 1606 # make sure body is rich text 1607 if ( $LayoutObject->{BrowserRichText} ) { 1608 $GetParam{Body} = $LayoutObject->Ascii2RichText( 1609 String => $GetParam{Body}, 1610 ); 1611 } 1612 1613 $Body = $GetParam{Body} . $Body; 1614 } 1615 1616 # fillup configured default vars 1617 if ( $Body eq '' && $Config->{Body} ) { 1618 $Body = $LayoutObject->Output( 1619 Template => $Config->{Body}, 1620 ); 1621 1622 # make sure body is rich text 1623 if ( $LayoutObject->{BrowserRichText} ) { 1624 $Body = $LayoutObject->Ascii2RichText( 1625 String => $Body, 1626 ); 1627 } 1628 } 1629 1630 # set Body var to calculated content 1631 $GetParam{Body} = $Body; 1632 1633 my %SafetyCheckResult = $Kernel::OM->Get('Kernel::System::HTMLUtils')->Safety( 1634 String => $GetParam{Body}, 1635 1636 # Strip out external content if BlockLoadingRemoteContent is enabled. 1637 NoExtSrcLoad => $ConfigObject->Get('Ticket::Frontend::BlockLoadingRemoteContent'), 1638 1639 # Disallow potentially unsafe content. 1640 NoApplet => 1, 1641 NoObject => 1, 1642 NoEmbed => 1, 1643 NoSVG => 1, 1644 NoJavaScript => 1, 1645 ); 1646 $GetParam{Body} = $SafetyCheckResult{String}; 1647 1648 if ( $Self->{ReplyToArticle} ) { 1649 my $TicketSubjectRe = $ConfigObject->Get('Ticket::SubjectRe') || 'Re'; 1650 $GetParam{Subject} = $TicketSubjectRe . ': ' . $Self->{ReplyToArticleContent}{Subject}; 1651 } 1652 elsif ( !defined $GetParam{Subject} && $Config->{Subject} ) { 1653 $GetParam{Subject} = $LayoutObject->Output( 1654 Template => $Config->{Subject}, 1655 ); 1656 } 1657 1658 my @TicketTypeDynamicFields; 1659 my @ArticleTypeDynamicFields; 1660 1661 # cycle trough the activated Dynamic Fields for this screen 1662 DYNAMICFIELD: 1663 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1664 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1665 1666 my $PossibleValuesFilter; 1667 1668 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 1669 DynamicFieldConfig => $DynamicFieldConfig, 1670 Behavior => 'IsACLReducible', 1671 ); 1672 1673 if ($IsACLReducible) { 1674 1675 # get PossibleValues 1676 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 1677 DynamicFieldConfig => $DynamicFieldConfig, 1678 ); 1679 1680 # check if field has PossibleValues property in its configuration 1681 if ( IsHashRefWithData($PossibleValues) ) { 1682 1683 # convert possible values key => value to key => key for ACLs using a Hash slice 1684 my %AclData = %{$PossibleValues}; 1685 @AclData{ keys %AclData } = keys %AclData; 1686 1687 # set possible values filter from ACLs 1688 my $ACL = $TicketObject->TicketAcl( 1689 %GetParam, 1690 Action => $Self->{Action}, 1691 TicketID => $Self->{TicketID}, 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 %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} } 1702 keys %Filter; 1703 } 1704 } 1705 } 1706 1707 # to store dynamic field value from database (or undefined) 1708 my $Value; 1709 1710 if ( $DynamicFieldConfig->{ObjectType} eq 'Ticket' ) { 1711 1712 # Only get values for Ticket fields (all screens based on AgentTickeActionCommon 1713 # generates a new article, then article fields will be always empty at the beginning). 1714 # Value is stored in the database from Ticket. 1715 $Value = $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} }; 1716 1717 # Get field html. 1718 my $DynamicFieldHTML = $DynamicFieldBackendObject->EditFieldRender( 1719 DynamicFieldConfig => $DynamicFieldConfig, 1720 PossibleValuesFilter => $PossibleValuesFilter, 1721 Value => $Value, 1722 Mandatory => $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2, 1723 LayoutObject => $LayoutObject, 1724 ParamObject => $ParamObject, 1725 AJAXUpdate => 1, 1726 UpdatableFields => $Self->_GetFieldsToUpdate(), 1727 ); 1728 1729 push @TicketTypeDynamicFields, { 1730 Name => $DynamicFieldConfig->{Name}, 1731 Label => $DynamicFieldHTML->{Label}, 1732 Field => $DynamicFieldHTML->{Field}, 1733 }; 1734 } 1735 elsif ( $DynamicFieldConfig->{ObjectType} eq 'Article' ) { 1736 my $Class = ''; 1737 my $MandatoryTooltip = 0; 1738 1739 if ( $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2 ) { 1740 if ( 1741 $Config->{NoteMandatory} || 1742 $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 1743 ) 1744 { 1745 $Class = 'Validate_Required'; 1746 } 1747 else { 1748 $Class = 'Validate_DependingRequiredAND Validate_Depending_CreateArticle'; 1749 $MandatoryTooltip = 1; 1750 } 1751 } 1752 1753 # Get field html. 1754 my $DynamicFieldHTML = $DynamicFieldBackendObject->EditFieldRender( 1755 DynamicFieldConfig => $DynamicFieldConfig, 1756 PossibleValuesFilter => $PossibleValuesFilter, 1757 Value => $Value, 1758 Mandatory => ( $Class eq 'Validate_Required' ) ? 1 : 0, 1759 Class => $Class, 1760 LayoutObject => $LayoutObject, 1761 ParamObject => $ParamObject, 1762 AJAXUpdate => 1, 1763 UpdatableFields => $Self->_GetFieldsToUpdate(), 1764 ); 1765 1766 push @ArticleTypeDynamicFields, { 1767 Name => $DynamicFieldConfig->{Name}, 1768 Label => $DynamicFieldHTML->{Label}, 1769 Field => $DynamicFieldHTML->{Field}, 1770 MandatoryTooltip => $MandatoryTooltip, 1771 }; 1772 } 1773 } 1774 1775 # print form ... 1776 my $Output = $LayoutObject->Header( 1777 Type => 'Small', 1778 Value => $Ticket{TicketNumber}, 1779 BodyClass => 'Popup', 1780 ); 1781 $Output .= $Self->_Mask( 1782 TimeUnitsRequired => ( 1783 $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 1784 ? 'Validate_Required' 1785 : '' 1786 ), 1787 TicketTypeDynamicFields => \@TicketTypeDynamicFields, 1788 ArticleTypeDynamicFields => \@ArticleTypeDynamicFields, 1789 %GetParam, 1790 %Ticket, 1791 ); 1792 $Output .= $LayoutObject->Footer( 1793 Type => 'Small', 1794 ); 1795 return $Output; 1796 } 1797} 1798 1799sub _Mask { 1800 my ( $Self, %Param ) = @_; 1801 1802 # get list type 1803 my $TreeView = 0; 1804 1805 # get config object 1806 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1807 1808 if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) { 1809 $TreeView = 1; 1810 } 1811 1812 # get needed objects 1813 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 1814 1815 my %Ticket = $TicketObject->TicketGet( TicketID => $Self->{TicketID} ); 1816 1817 # get config of frontend module 1818 my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}"); 1819 1820 # get layout object 1821 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 1822 1823 # Define the dynamic fields to show based on the object type. 1824 my $ObjectType = ['Ticket']; 1825 1826 # Only screens that add notes can modify Article dynamic fields. 1827 if ( $Config->{Note} ) { 1828 $ObjectType = [ 'Ticket', 'Article' ]; 1829 } 1830 1831 # Get dynamic fields for this screen. 1832 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 1833 Valid => 1, 1834 ObjectType => $ObjectType, 1835 FieldFilter => $Config->{DynamicField} || {}, 1836 ); 1837 1838 # Widget Ticket Actions 1839 if ( 1840 ( $ConfigObject->Get('Ticket::Type') && $Config->{TicketType} ) 1841 || 1842 ( $ConfigObject->Get('Ticket::Service') && $Config->{Service} ) || 1843 ( $ConfigObject->Get('Ticket::Responsible') && $Config->{Responsible} ) || 1844 $Config->{Title} || 1845 $Config->{Queue} || 1846 $Config->{Owner} || 1847 $Config->{State} || 1848 $Config->{Priority} || 1849 scalar @{ $Param{TicketTypeDynamicFields} } > 0 1850 ) 1851 { 1852 $LayoutObject->Block( 1853 Name => 'WidgetTicketActions', 1854 ); 1855 } 1856 1857 if ( $Config->{Title} ) { 1858 $LayoutObject->Block( 1859 Name => 'Title', 1860 Data => \%Param, 1861 ); 1862 } 1863 1864 my $DynamicFieldNames = $Self->_GetFieldsToUpdate( 1865 OnlyDynamicFields => 1, 1866 ); 1867 1868 # send data to JS 1869 $LayoutObject->AddJSData( 1870 Key => 'DynamicFieldNames', 1871 Value => $DynamicFieldNames, 1872 ); 1873 1874 # types 1875 if ( $ConfigObject->Get('Ticket::Type') && $Config->{TicketType} ) { 1876 my %Type = $TicketObject->TicketTypeList( 1877 %Param, 1878 Action => $Self->{Action}, 1879 UserID => $Self->{UserID}, 1880 ); 1881 $Param{TypeStrg} = $LayoutObject->BuildSelection( 1882 Class => 'Validate_Required Modernize ' . ( $Param{Errors}->{TypeIDInvalid} || '' ), 1883 Data => \%Type, 1884 Name => 'TypeID', 1885 SelectedID => $Param{TypeID}, 1886 PossibleNone => 1, 1887 Sort => 'AlphanumericValue', 1888 Translation => 0, 1889 ); 1890 $LayoutObject->Block( 1891 Name => 'Type', 1892 Data => {%Param}, 1893 ); 1894 } 1895 1896 # services 1897 if ( $ConfigObject->Get('Ticket::Service') && $Config->{Service} ) { 1898 my $Services = $Self->_GetServices( 1899 %Param, 1900 Action => $Self->{Action}, 1901 CustomerUserID => $Ticket{CustomerUserID}, 1902 UserID => $Self->{UserID}, 1903 ); 1904 1905 # reset previous ServiceID to reset SLA-List if no service is selected 1906 if ( !$Param{ServiceID} || !$Services->{ $Param{ServiceID} } ) { 1907 $Param{ServiceID} = ''; 1908 } 1909 1910 $Param{ServiceStrg} = $LayoutObject->BuildSelection( 1911 Data => $Services, 1912 Name => 'ServiceID', 1913 SelectedID => $Param{ServiceID}, 1914 Class => "Modernize " 1915 . ( $Config->{ServiceMandatory} ? 'Validate_Required ' : '' ) 1916 . ( $Param{ServiceInvalid} || '' ), 1917 PossibleNone => 1, 1918 TreeView => $TreeView, 1919 Sort => 'TreeView', 1920 Translation => 0, 1921 Max => 200, 1922 ); 1923 1924 $LayoutObject->Block( 1925 Name => 'Service', 1926 Data => { 1927 ServiceMandatory => $Config->{ServiceMandatory} || 0, 1928 %Param, 1929 }, 1930 ); 1931 1932 my %SLA = $TicketObject->TicketSLAList( 1933 %Param, 1934 Action => $Self->{Action}, 1935 UserID => $Self->{UserID}, 1936 ); 1937 1938 $Param{SLAStrg} = $LayoutObject->BuildSelection( 1939 Data => \%SLA, 1940 Name => 'SLAID', 1941 SelectedID => $Param{SLAID}, 1942 Class => "Modernize " 1943 . ( $Config->{SLAMandatory} ? 'Validate_Required ' : '' ) 1944 . ( $Param{ServiceInvalid} || '' ), 1945 PossibleNone => 1, 1946 Sort => 'AlphanumericValue', 1947 Translation => 0, 1948 Max => 200, 1949 ); 1950 1951 $LayoutObject->Block( 1952 Name => 'SLA', 1953 Data => { 1954 SLAMandatory => $Config->{SLAMandatory}, 1955 %Param, 1956 }, 1957 ); 1958 } 1959 1960 if ( $Config->{Queue} ) { 1961 1962 # fetch all queues 1963 my %MoveQueues = $TicketObject->TicketMoveList( 1964 TicketID => $Self->{TicketID}, 1965 UserID => $Self->{UserID}, 1966 Action => $Self->{Action}, 1967 Type => 'move_into', 1968 ); 1969 1970 # set move queues 1971 $Param{QueuesStrg} = $LayoutObject->AgentQueueListOption( 1972 Data => { %MoveQueues, '' => '-' }, 1973 Multiple => 0, 1974 Size => 0, 1975 Class => 'NewQueueID Modernize ' 1976 . ( $Config->{QueueMandatory} ? 'Validate_Required ' : '' ) 1977 . ( $Param{NewQueueInvalid} || '' ), 1978 Name => 'NewQueueID', 1979 SelectedID => $Param{NewQueueID}, 1980 TreeView => $TreeView, 1981 CurrentQueueID => $Param{QueueID}, 1982 OnChangeSubmit => 0, 1983 ); 1984 1985 $LayoutObject->Block( 1986 Name => 'Queue', 1987 Data => { 1988 QueueMandatory => $Config->{QueueMandatory} || 0, 1989 %Param 1990 }, 1991 ); 1992 } 1993 1994 # get needed objects 1995 my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); 1996 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 1997 my $GroupObject = $Kernel::OM->Get('Kernel::System::Group'); 1998 1999 if ( $Config->{Owner} ) { 2000 2001 # get user of own groups 2002 my %ShownUsers; 2003 my %AllGroupsMembers = $UserObject->UserList( 2004 Type => 'Long', 2005 Valid => 1, 2006 ); 2007 if ( $ConfigObject->Get('Ticket::ChangeOwnerToEveryone') ) { 2008 %ShownUsers = %AllGroupsMembers; 2009 } 2010 else { 2011 my $GID = $QueueObject->GetQueueGroupID( QueueID => $Ticket{QueueID} ); 2012 my %MemberList = $GroupObject->PermissionGroupGet( 2013 GroupID => $GID, 2014 Type => 'owner', 2015 ); 2016 for my $UserID ( sort keys %MemberList ) { 2017 $ShownUsers{$UserID} = $AllGroupsMembers{$UserID}; 2018 } 2019 } 2020 2021 my $ACL = $TicketObject->TicketAcl( 2022 %Ticket, 2023 Action => $Self->{Action}, 2024 ReturnType => 'Ticket', 2025 ReturnSubType => 'NewOwner', 2026 Data => \%ShownUsers, 2027 UserID => $Self->{UserID}, 2028 ); 2029 2030 if ($ACL) { 2031 %ShownUsers = $TicketObject->TicketAclData(); 2032 } 2033 2034 # get old owner 2035 my @OldUserInfo = $TicketObject->TicketOwnerList( TicketID => $Self->{TicketID} ); 2036 my @OldOwners; 2037 my %OldOwnersShown; 2038 my %SeenOldOwner; 2039 if (@OldUserInfo) { 2040 my $Counter = 1; 2041 USER: 2042 for my $User ( reverse @OldUserInfo ) { 2043 2044 # skip if old owner is already in the list 2045 next USER if $SeenOldOwner{ $User->{UserID} }; 2046 $SeenOldOwner{ $User->{UserID} } = 1; 2047 my $Key = $User->{UserID}; 2048 my $Value = "$Counter: $User->{UserFullname}"; 2049 push @OldOwners, { 2050 Key => $Key, 2051 Value => $Value, 2052 }; 2053 $OldOwnersShown{$Key} = $Value; 2054 $Counter++; 2055 } 2056 } 2057 2058 my $OldOwnerSelectedID = ''; 2059 if ( $Param{OldOwnerID} ) { 2060 $OldOwnerSelectedID = $Param{OldOwnerID}; 2061 } 2062 elsif ( $OldUserInfo[0]->{UserID} ) { 2063 $OldOwnerSelectedID = $OldUserInfo[0]->{UserID} . '1'; 2064 } 2065 2066 my $OldOwnerACL = $TicketObject->TicketAcl( 2067 %Ticket, 2068 Action => $Self->{Action}, 2069 ReturnType => 'Ticket', 2070 ReturnSubType => 'OldOwner', 2071 Data => \%OldOwnersShown, 2072 UserID => $Self->{UserID}, 2073 ); 2074 2075 if ($OldOwnerACL) { 2076 %OldOwnersShown = $TicketObject->TicketAclData(); 2077 } 2078 2079 # build string 2080 $Param{OwnerStrg} = $LayoutObject->BuildSelection( 2081 Data => \%ShownUsers, 2082 SelectedID => $Param{NewOwnerID}, 2083 Name => 'NewOwnerID', 2084 Class => 'Modernize ' 2085 . ( $Config->{OwnerMandatory} ? 'Validate_Required ' : '' ) 2086 . ( $Param{NewOwnerInvalid} || '' ), 2087 Size => 1, 2088 PossibleNone => 1, 2089 Filters => { 2090 OldOwners => { 2091 Name => $LayoutObject->{LanguageObject}->Translate('Previous Owner'), 2092 Values => \%OldOwnersShown, 2093 }, 2094 }, 2095 ); 2096 2097 $LayoutObject->Block( 2098 Name => 'Owner', 2099 Data => { 2100 OwnerMandatory => $Config->{OwnerMandatory} || 0, 2101 %Param, 2102 }, 2103 ); 2104 } 2105 2106 if ( $ConfigObject->Get('Ticket::Responsible') && $Config->{Responsible} ) { 2107 2108 # get user of own groups 2109 my %ShownUsers; 2110 my %AllGroupsMembers = $UserObject->UserList( 2111 Type => 'Long', 2112 Valid => 1, 2113 ); 2114 if ( $ConfigObject->Get('Ticket::ChangeOwnerToEveryone') ) { 2115 %ShownUsers = %AllGroupsMembers; 2116 } 2117 else { 2118 my $GID = $QueueObject->GetQueueGroupID( QueueID => $Ticket{QueueID} ); 2119 my %MemberList = $GroupObject->PermissionGroupGet( 2120 GroupID => $GID, 2121 Type => 'responsible', 2122 ); 2123 for my $UserID ( sort keys %MemberList ) { 2124 $ShownUsers{$UserID} = $AllGroupsMembers{$UserID}; 2125 } 2126 } 2127 2128 my $ACL = $TicketObject->TicketAcl( 2129 %Ticket, 2130 Action => $Self->{Action}, 2131 ReturnType => 'Ticket', 2132 ReturnSubType => 'Responsible', 2133 Data => \%ShownUsers, 2134 UserID => $Self->{UserID}, 2135 ); 2136 2137 if ($ACL) { 2138 %ShownUsers = $TicketObject->TicketAclData(); 2139 } 2140 2141 # get responsible 2142 $Param{ResponsibleStrg} = $LayoutObject->BuildSelection( 2143 Data => \%ShownUsers, 2144 SelectedID => $Param{NewResponsibleID}, 2145 Name => 'NewResponsibleID', 2146 Class => 'Modernize ' 2147 . ( $Config->{ResponsibleMandatory} ? 'Validate_Required ' : '' ) 2148 . ( $Param{NewResponsibleInvalid} || '' ), 2149 PossibleNone => 1, 2150 Size => 1, 2151 ); 2152 $LayoutObject->Block( 2153 Name => 'Responsible', 2154 Data => { 2155 ResponsibleMandatory => $Config->{ResponsibleMandatory} || 0, 2156 %Param, 2157 }, 2158 ); 2159 2160 } 2161 2162 if ( $Config->{State} ) { 2163 2164 my %State; 2165 my %StateList = $TicketObject->TicketStateList( 2166 Action => $Self->{Action}, 2167 TicketID => $Self->{TicketID}, 2168 UserID => $Self->{UserID}, 2169 ); 2170 if ( !$Param{NewStateID} ) { 2171 if ( $Config->{StateDefault} ) { 2172 $State{SelectedValue} = $Config->{StateDefault}; 2173 } 2174 } 2175 else { 2176 $State{SelectedID} = $Param{NewStateID}; 2177 } 2178 2179 # build next states string 2180 $Param{StateStrg} = $LayoutObject->BuildSelection( 2181 Data => \%StateList, 2182 Name => 'NewStateID', 2183 Class => 'Modernize ' 2184 . ( $Config->{StateMandatory} ? 'Validate_Required ' : '' ) 2185 . ( $Param{NewStateInvalid} || '' ), 2186 PossibleNone => $Config->{StateDefault} ? 0 : 1, 2187 %State, 2188 ); 2189 $LayoutObject->Block( 2190 Name => 'State', 2191 Data => { 2192 StateMandatory => $Config->{StateMandatory} || 0, 2193 %Param, 2194 }, 2195 ); 2196 2197 if ( IsArrayRefWithData( $Config->{StateType} ) ) { 2198 2199 STATETYPE: 2200 for my $StateType ( @{ $Config->{StateType} } ) { 2201 2202 next STATETYPE if !$StateType; 2203 next STATETYPE if $StateType !~ /pending/i; 2204 2205 # get used calendar 2206 my $Calendar = $TicketObject->TicketCalendarGet( 2207 %Ticket, 2208 ); 2209 2210 $Param{DateString} = $LayoutObject->BuildDateSelection( 2211 %Param, 2212 Format => 'DateInputFormatLong', 2213 YearPeriodPast => 0, 2214 YearPeriodFuture => 5, 2215 DiffTime => $ConfigObject->Get('Ticket::Frontend::PendingDiffTime') 2216 || 0, 2217 Class => $Param{DateInvalid} || ' ', 2218 Validate => 1, 2219 ValidateDateInFuture => 1, 2220 Calendar => $Calendar, 2221 ); 2222 2223 $LayoutObject->Block( 2224 Name => 'StatePending', 2225 Data => \%Param, 2226 ); 2227 2228 last STATETYPE; 2229 } 2230 } 2231 } 2232 2233 # get priority 2234 if ( $Config->{Priority} ) { 2235 2236 my %Priority; 2237 my %PriorityList = $TicketObject->TicketPriorityList( 2238 UserID => $Self->{UserID}, 2239 TicketID => $Self->{TicketID}, 2240 Action => $Self->{Action}, 2241 ); 2242 if ( !$Config->{PriorityDefault} ) { 2243 $PriorityList{''} = '-'; 2244 } 2245 if ( !$Param{NewPriorityID} ) { 2246 if ( $Config->{PriorityDefault} ) { 2247 $Priority{SelectedValue} = $Config->{PriorityDefault}; 2248 } 2249 } 2250 else { 2251 $Priority{SelectedID} = $Param{NewPriorityID}; 2252 } 2253 $Priority{SelectedID} ||= $Param{PriorityID}; 2254 $Param{PriorityStrg} = $LayoutObject->BuildSelection( 2255 Data => \%PriorityList, 2256 Name => 'NewPriorityID', 2257 Class => 'Modernize', 2258 %Priority, 2259 ); 2260 $LayoutObject->Block( 2261 Name => 'Priority', 2262 Data => \%Param, 2263 ); 2264 } 2265 2266 # Get Ticket type dynamic fields. 2267 for my $TicketTypeDynamicField ( @{ $Param{TicketTypeDynamicFields} } ) { 2268 $LayoutObject->Block( 2269 Name => 'TicketTypeDynamicField', 2270 Data => $TicketTypeDynamicField, 2271 ); 2272 2273 # Output customization block too, if it exists. 2274 $LayoutObject->Block( 2275 Name => 'TicketTypeDynamicField_' . $TicketTypeDynamicField->{Name}, 2276 Data => $TicketTypeDynamicField, 2277 ); 2278 } 2279 2280 # End Widget Ticket Actions 2281 2282 # Widget Article 2283 if ( $Config->{Note} ) { 2284 2285 $Param{WidgetStatus} = 'Collapsed'; 2286 2287 if ( 2288 $Config->{NoteMandatory} 2289 || $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 2290 || $Self->{ReplyToArticle} 2291 || $Param{CreateArticle} 2292 ) 2293 { 2294 $Param{WidgetStatus} = 'Expanded'; 2295 } 2296 2297 if ( 2298 $Config->{NoteMandatory} 2299 || $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 2300 ) 2301 { 2302 $Param{SubjectRequired} = 'Validate_Required'; 2303 $Param{BodyRequired} = 'Validate_Required'; 2304 } 2305 else { 2306 $Param{SubjectRequired} = 'Validate_DependingRequiredAND Validate_Depending_CreateArticle'; 2307 $Param{BodyRequired} = 'Validate_DependingRequiredAND Validate_Depending_CreateArticle'; 2308 } 2309 2310 # set customer visibility of this note to the same value as the article for whom this is the reply 2311 if ( $Self->{ReplyToArticle} && !defined $Param{IsVisibleForCustomer} ) { 2312 $Param{IsVisibleForCustomer} = $Self->{ReplyToArticleContent}->{IsVisibleForCustomer}; 2313 } 2314 elsif ( !defined $Param{IsVisibleForCustomer} ) { 2315 $Param{IsVisibleForCustomer} = $Config->{IsVisibleForCustomerDefault}; 2316 } 2317 2318 # show attachments 2319 ATTACHMENT: 2320 for my $Attachment ( @{ $Param{Attachments} } ) { 2321 if ( 2322 $Attachment->{ContentID} 2323 && $LayoutObject->{BrowserRichText} 2324 && ( $Attachment->{ContentType} =~ /image/i ) 2325 && ( $Attachment->{Disposition} eq 'inline' ) 2326 ) 2327 { 2328 next ATTACHMENT; 2329 } 2330 2331 push @{ $Param{AttachmentList} }, $Attachment; 2332 } 2333 2334 $LayoutObject->Block( 2335 Name => 'WidgetArticle', 2336 Data => {%Param}, 2337 ); 2338 2339 # get all user ids of agents, that can be shown in this dialog 2340 # based on queue rights 2341 my %ShownUsers; 2342 my %AllGroupsMembers = $UserObject->UserList( 2343 Type => 'Long', 2344 Valid => 1, 2345 ); 2346 my $GID = $QueueObject->GetQueueGroupID( QueueID => $Ticket{QueueID} ); 2347 my %MemberList = $GroupObject->PermissionGroupGet( 2348 GroupID => $GID, 2349 Type => 'note', 2350 ); 2351 for my $UserID ( sort keys %MemberList ) { 2352 $ShownUsers{$UserID} = $AllGroupsMembers{$UserID}; 2353 } 2354 2355 # create email parser object 2356 my $EmailParserObject = Kernel::System::EmailParser->new( 2357 Mode => 'Standalone', 2358 Debug => 0, 2359 ); 2360 2361 # check and retrieve involved and informed agents of ReplyTo Note 2362 my @ReplyToUsers; 2363 my %ReplyToUsersHash; 2364 my %ReplyToUserIDs; 2365 if ( $Self->{ReplyToArticle} ) { 2366 my @ReplyToParts = $EmailParserObject->SplitAddressLine( 2367 Line => $Self->{ReplyToArticleContent}->{To} || '', 2368 ); 2369 2370 REPLYTOPART: 2371 for my $SingleReplyToPart (@ReplyToParts) { 2372 my $ReplyToAddress = $EmailParserObject->GetEmailAddress( 2373 Email => $SingleReplyToPart, 2374 ); 2375 2376 next REPLYTOPART if !$ReplyToAddress; 2377 push @ReplyToUsers, $ReplyToAddress; 2378 } 2379 2380 $ReplyToUsersHash{$_}++ for @ReplyToUsers; 2381 2382 # get user ids of available users 2383 for my $UserID ( sort keys %ShownUsers ) { 2384 my %UserData = $UserObject->GetUserData( 2385 UserID => $UserID, 2386 ); 2387 2388 my $UserEmail = $UserData{UserEmail}; 2389 if ( $ReplyToUsersHash{$UserEmail} ) { 2390 $ReplyToUserIDs{$UserID} = 1; 2391 } 2392 } 2393 2394 # add original note sender to list of user ids 2395 for my $UserID ( sort @{ $Self->{ReplyToSenderUserID} } ) { 2396 2397 # if sender replies to himself, do not include sender in list 2398 if ( $UserID ne $Self->{UserID} ) { 2399 $ReplyToUserIDs{$UserID} = 1; 2400 } 2401 } 2402 2403 # remove user id of active user 2404 delete $ReplyToUserIDs{ $Self->{UserID} }; 2405 } 2406 2407 if ( $Config->{InformAgent} || $Config->{InvolvedAgent} ) { 2408 $LayoutObject->Block( 2409 Name => 'InformAdditionalAgents', 2410 ); 2411 } 2412 2413 # get param object 2414 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 2415 2416 # get all agents for "involved agents" 2417 if ( $Config->{InvolvedAgent} ) { 2418 2419 my @UserIDs = $TicketObject->TicketInvolvedAgentsList( 2420 TicketID => $Self->{TicketID}, 2421 ); 2422 2423 my @InvolvedAgents; 2424 my $Counter = 1; 2425 2426 my @InvolvedUserID = $ParamObject->GetArray( Param => 'InvolvedUserID' ); 2427 2428 my %AgentWithPermission = $GroupObject->PermissionGroupGet( 2429 GroupID => $GID, 2430 Type => 'ro', 2431 ); 2432 2433 USER: 2434 for my $User ( reverse @UserIDs ) { 2435 2436 next USER if !defined $AgentWithPermission{ $User->{UserID} }; 2437 2438 my $Value = "$Counter: $User->{UserFullname}"; 2439 if ( $User->{OutOfOfficeMessage} ) { 2440 $Value .= " $User->{OutOfOfficeMessage}"; 2441 } 2442 2443 push @InvolvedAgents, { 2444 Key => $User->{UserID}, 2445 Value => $Value, 2446 }; 2447 $Counter++; 2448 2449 # add involved user as selected entries, if available in ReplyToAddresses list 2450 if ( $Self->{ReplyToArticle} && $ReplyToUserIDs{ $User->{UserID} } ) { 2451 push @InvolvedUserID, $User->{UserID}; 2452 delete $ReplyToUserIDs{ $User->{UserID} }; 2453 } 2454 } 2455 2456 my $InvolvedAgentSize = $ConfigObject->Get('Ticket::Frontend::InvolvedAgentMaxSize') || 3; 2457 $Param{InvolvedAgentStrg} = $LayoutObject->BuildSelection( 2458 Data => \@InvolvedAgents, 2459 SelectedID => \@InvolvedUserID, 2460 Name => 'InvolvedUserID', 2461 Class => 'Modernize', 2462 Multiple => 1, 2463 Size => $InvolvedAgentSize, 2464 ); 2465 2466 # block is called below "inform agents" 2467 } 2468 2469 # agent list 2470 if ( $Config->{InformAgent} ) { 2471 2472 # get inform user list 2473 my %InformAgents; 2474 my @InformUserID = $ParamObject->GetArray( Param => 'InformUserID' ); 2475 my %InformAgentList = $GroupObject->PermissionGroupGet( 2476 GroupID => $GID, 2477 Type => 'ro', 2478 ); 2479 for my $UserID ( sort keys %InformAgentList ) { 2480 $InformAgents{$UserID} = $AllGroupsMembers{$UserID}; 2481 } 2482 2483 if ( $Self->{ReplyToArticle} ) { 2484 2485 # get email address of all users and compare to replyto-addresses 2486 for my $UserID ( sort keys %InformAgents ) { 2487 if ( $ReplyToUserIDs{$UserID} ) { 2488 push @InformUserID, $UserID; 2489 delete $ReplyToUserIDs{$UserID}; 2490 } 2491 } 2492 } 2493 2494 my $InformAgentSize = $ConfigObject->Get('Ticket::Frontend::InformAgentMaxSize') 2495 || 3; 2496 $Param{OptionStrg} = $LayoutObject->BuildSelection( 2497 Data => \%InformAgents, 2498 SelectedID => \@InformUserID, 2499 Name => 'InformUserID', 2500 Class => 'Modernize', 2501 Multiple => 1, 2502 Size => $InformAgentSize, 2503 ); 2504 $LayoutObject->Block( 2505 Name => 'InformAgent', 2506 Data => \%Param, 2507 ); 2508 } 2509 2510 # get involved 2511 if ( $Config->{InvolvedAgent} ) { 2512 2513 $LayoutObject->Block( 2514 Name => 'InvolvedAgent', 2515 Data => \%Param, 2516 ); 2517 } 2518 2519 # show list of agents, that receive this note (ReplyToNote) 2520 # at least sender of original note and all recepients of the original note 2521 # that couldn't be selected with involved/inform agents 2522 if ( $Self->{ReplyToArticle} ) { 2523 2524 my $UsersHashSize = keys %ReplyToUserIDs; 2525 my $Counter = 0; 2526 $Param{UserListWithoutSelection} = join( ',', keys %ReplyToUserIDs ); 2527 2528 if ( $UsersHashSize > 0 ) { 2529 $LayoutObject->Block( 2530 Name => 'InformAgentsWithoutSelection', 2531 Data => \%Param, 2532 ); 2533 2534 for my $UserID ( sort keys %ReplyToUserIDs ) { 2535 $Counter++; 2536 2537 my %UserData = $UserObject->GetUserData( 2538 UserID => $UserID, 2539 ); 2540 2541 $LayoutObject->Block( 2542 Name => 'InformAgentsWithoutSelectionSingleUser', 2543 Data => \%UserData, 2544 ); 2545 2546 # output a separator (InformAgentsWithoutSelectionSingleUserSeparator), 2547 # if not last entry 2548 if ( $Counter < $UsersHashSize ) { 2549 $LayoutObject->Block( 2550 Name => 'InformAgentsWithoutSelectionSingleUserSeparator', 2551 Data => \%UserData, 2552 ); 2553 } 2554 } 2555 } 2556 } 2557 2558 # add rich text editor 2559 if ( $LayoutObject->{BrowserRichText} ) { 2560 2561 # use height/width defined for this screen 2562 $Param{RichTextHeight} = $Config->{RichTextHeight} || 0; 2563 $Param{RichTextWidth} = $Config->{RichTextWidth} || 0; 2564 2565 # set up rich text editor 2566 $LayoutObject->SetRichTextParameters( 2567 Data => \%Param, 2568 ); 2569 } 2570 2571 if ( 2572 $Config->{NoteMandatory} 2573 || $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 2574 ) 2575 { 2576 $LayoutObject->Block( 2577 Name => 'SubjectLabelMandatory', 2578 ); 2579 $LayoutObject->Block( 2580 Name => 'RichTextLabelMandatory', 2581 ); 2582 } 2583 else { 2584 $LayoutObject->Block( 2585 Name => 'SubjectLabel', 2586 ); 2587 $LayoutObject->Block( 2588 Name => 'RichTextLabel', 2589 ); 2590 } 2591 2592 # build text template string 2593 my %StandardTemplates = $Kernel::OM->Get('Kernel::System::StandardTemplate')->StandardTemplateList( 2594 Valid => 1, 2595 Type => 'Note', 2596 ); 2597 2598 my $QueueStandardTemplates = $Self->_GetStandardTemplates( 2599 %Param, 2600 TicketID => $Self->{TicketID} || '', 2601 ); 2602 2603 if ( 2604 IsHashRefWithData( 2605 $QueueStandardTemplates 2606 || ( $Config->{Queue} && IsHashRefWithData( \%StandardTemplates ) ) 2607 ) 2608 ) 2609 { 2610 $Param{StandardTemplateStrg} = $LayoutObject->BuildSelection( 2611 Data => $QueueStandardTemplates || {}, 2612 Name => 'StandardTemplateID', 2613 SelectedID => $Param{StandardTemplateID} || '', 2614 Class => 'Modernize', 2615 PossibleNone => 1, 2616 Sort => 'AlphanumericValue', 2617 Translation => 1, 2618 Max => 200, 2619 ); 2620 $LayoutObject->Block( 2621 Name => 'StandardTemplate', 2622 Data => {%Param}, 2623 ); 2624 } 2625 2626 # show time accounting box 2627 if ( $ConfigObject->Get('Ticket::Frontend::AccountTime') ) { 2628 if ( $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') ) { 2629 $LayoutObject->Block( 2630 Name => 'TimeUnitsLabelMandatory', 2631 Data => \%Param, 2632 ); 2633 } 2634 else { 2635 $LayoutObject->Block( 2636 Name => 'TimeUnitsLabel', 2637 Data => \%Param, 2638 ); 2639 } 2640 $LayoutObject->Block( 2641 Name => 'TimeUnits', 2642 Data => \%Param, 2643 ); 2644 } 2645 2646 # Get Article type dynamic fields. 2647 for my $ArticleTypeDynamicField ( @{ $Param{ArticleTypeDynamicFields} } ) { 2648 $LayoutObject->Block( 2649 Name => 'ArticleTypeDynamicField', 2650 Data => $ArticleTypeDynamicField, 2651 ); 2652 2653 if ( $ArticleTypeDynamicField->{MandatoryTooltip} ) { 2654 $LayoutObject->Block( 2655 Name => 'ArticleTypeDynamicFieldError', 2656 Data => $ArticleTypeDynamicField, 2657 ); 2658 } 2659 2660 # Output customization block too, if it exists. 2661 $LayoutObject->Block( 2662 Name => 'ArticleTypeDynamicField_' . $ArticleTypeDynamicField->{Name}, 2663 Data => $ArticleTypeDynamicField, 2664 ); 2665 } 2666 } 2667 2668 # End Widget Article 2669 2670 # get output back 2671 return $LayoutObject->Output( 2672 TemplateFile => $Self->{Action}, 2673 Data => \%Param 2674 ); 2675} 2676 2677sub _GetNextStates { 2678 my ( $Self, %Param ) = @_; 2679 2680 my %NextStates = $Kernel::OM->Get('Kernel::System::Ticket')->TicketStateList( 2681 TicketID => $Self->{TicketID}, 2682 Action => $Self->{Action}, 2683 UserID => $Self->{UserID}, 2684 %Param, 2685 ); 2686 2687 return \%NextStates; 2688} 2689 2690sub _GetResponsible { 2691 my ( $Self, %Param ) = @_; 2692 my %ShownUsers; 2693 my %AllGroupsMembers = $Kernel::OM->Get('Kernel::System::User')->UserList( 2694 Type => 'Long', 2695 Valid => 1, 2696 ); 2697 2698 # show all users 2699 if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::ChangeOwnerToEveryone') ) { 2700 %ShownUsers = %AllGroupsMembers; 2701 } 2702 2703 # show only users with responsible or rw pemissions in the queue 2704 elsif ( $Param{QueueID} && !$Param{AllUsers} ) { 2705 my $GID = $Kernel::OM->Get('Kernel::System::Queue')->GetQueueGroupID( 2706 QueueID => $Param{NewQueueID} || $Param{QueueID} 2707 ); 2708 my %MemberList = $Kernel::OM->Get('Kernel::System::Group')->PermissionGroupGet( 2709 GroupID => $GID, 2710 Type => 'responsible', 2711 ); 2712 for my $UserID ( sort keys %MemberList ) { 2713 $ShownUsers{$UserID} = $AllGroupsMembers{$UserID}; 2714 } 2715 } 2716 2717 # get ticket object 2718 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 2719 2720 # workflow 2721 my $ACL = $TicketObject->TicketAcl( 2722 %Param, 2723 Action => $Self->{Action}, 2724 ReturnType => 'Ticket', 2725 ReturnSubType => 'Responsible', 2726 Data => \%ShownUsers, 2727 UserID => $Self->{UserID}, 2728 ); 2729 2730 return { $TicketObject->TicketAclData() } if $ACL; 2731 2732 return \%ShownUsers; 2733} 2734 2735sub _GetOwners { 2736 my ( $Self, %Param ) = @_; 2737 my %ShownUsers; 2738 my %AllGroupsMembers = $Kernel::OM->Get('Kernel::System::User')->UserList( 2739 Type => 'Long', 2740 Valid => 1, 2741 ); 2742 2743 # show all users 2744 if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::ChangeOwnerToEveryone') ) { 2745 %ShownUsers = %AllGroupsMembers; 2746 } 2747 2748 # show only users with owner or rw pemissions in the queue 2749 elsif ( $Param{QueueID} && !$Param{AllUsers} ) { 2750 my $GID = $Kernel::OM->Get('Kernel::System::Queue')->GetQueueGroupID( 2751 QueueID => $Param{NewQueueID} || $Param{QueueID} 2752 ); 2753 my %MemberList = $Kernel::OM->Get('Kernel::System::Group')->PermissionGroupGet( 2754 GroupID => $GID, 2755 Type => 'owner', 2756 ); 2757 for my $UserID ( sort keys %MemberList ) { 2758 $ShownUsers{$UserID} = $AllGroupsMembers{$UserID}; 2759 } 2760 } 2761 2762 # get ticket object 2763 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 2764 2765 # workflow 2766 my $ACL = $TicketObject->TicketAcl( 2767 %Param, 2768 Action => $Self->{Action}, 2769 ReturnType => 'Ticket', 2770 ReturnSubType => 'NewOwner', 2771 Data => \%ShownUsers, 2772 UserID => $Self->{UserID}, 2773 ); 2774 2775 return { $TicketObject->TicketAclData() } if $ACL; 2776 2777 return \%ShownUsers; 2778} 2779 2780sub _GetOldOwners { 2781 my ( $Self, %Param ) = @_; 2782 2783 # get ticket object 2784 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 2785 2786 my @OldUserInfo = $TicketObject->TicketOwnerList( TicketID => $Self->{TicketID} ); 2787 my %UserHash; 2788 if (@OldUserInfo) { 2789 my $Counter = 1; 2790 USER: 2791 for my $User ( reverse @OldUserInfo ) { 2792 2793 next USER if $UserHash{ $User->{UserID} }; 2794 2795 $UserHash{ $User->{UserID} } = "$Counter: $User->{UserFullname}"; 2796 $Counter++; 2797 } 2798 } 2799 2800 # workflow 2801 my $ACL = $TicketObject->TicketAcl( 2802 %Param, 2803 Action => $Self->{Action}, 2804 ReturnType => 'Ticket', 2805 ReturnSubType => 'OldOwner', 2806 Data => \%UserHash, 2807 UserID => $Self->{UserID}, 2808 ); 2809 2810 return { $TicketObject->TicketAclData() } if $ACL; 2811 2812 return \%UserHash; 2813} 2814 2815sub _GetServices { 2816 my ( $Self, %Param ) = @_; 2817 2818 # get service 2819 my %Service; 2820 2821 # get options for default services for unknown customers 2822 my $DefaultServiceUnknownCustomer 2823 = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service::Default::UnknownCustomer'); 2824 2825 # check if no CustomerUserID is selected 2826 # if $DefaultServiceUnknownCustomer = 0 leave CustomerUserID empty, it will not get any services 2827 # if $DefaultServiceUnknownCustomer = 1 set CustomerUserID to get default services 2828 if ( !$Param{CustomerUserID} && $DefaultServiceUnknownCustomer ) { 2829 $Param{CustomerUserID} = '<DEFAULT>'; 2830 } 2831 2832 # get service list 2833 if ( $Param{CustomerUserID} ) { 2834 %Service = $Kernel::OM->Get('Kernel::System::Ticket')->TicketServiceList( 2835 %Param, 2836 TicketID => $Self->{TicketID}, 2837 Action => $Self->{Action}, 2838 UserID => $Self->{UserID}, 2839 ); 2840 } 2841 return \%Service; 2842} 2843 2844sub _GetSLAs { 2845 my ( $Self, %Param ) = @_; 2846 2847 # if non set customers can get default services then they should also be able to get the SLAs 2848 # for those services (this works during ticket creation). 2849 # if no CustomerUserID is set, TicketSLAList will complain during AJAX updates as UserID is not 2850 # passed. See bug 11147. 2851 2852 # get options for default services for unknown customers 2853 my $DefaultServiceUnknownCustomer 2854 = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service::Default::UnknownCustomer'); 2855 2856 # check if no CustomerUserID is selected 2857 # if $DefaultServiceUnknownCustomer = 0 leave CustomerUserID empty, it will not get any services 2858 # if $DefaultServiceUnknownCustomer = 1 set CustomerUserID to get default services 2859 if ( !$Param{CustomerUserID} && $DefaultServiceUnknownCustomer ) { 2860 $Param{CustomerUserID} = '<DEFAULT>'; 2861 } 2862 2863 my %SLA; 2864 if ( $Param{ServiceID} ) { 2865 %SLA = $Kernel::OM->Get('Kernel::System::Ticket')->TicketSLAList( 2866 %Param, 2867 TicketID => $Self->{TicketID}, 2868 Action => $Self->{Action}, 2869 UserID => $Self->{UserID}, 2870 ); 2871 } 2872 return \%SLA; 2873} 2874 2875sub _GetPriorities { 2876 my ( $Self, %Param ) = @_; 2877 2878 my %Priorities = $Kernel::OM->Get('Kernel::System::Ticket')->TicketPriorityList( 2879 %Param, 2880 Action => $Self->{Action}, 2881 UserID => $Self->{UserID}, 2882 TicketID => $Self->{TicketID}, 2883 ); 2884 2885 # get config of frontend module 2886 my $Config = $Kernel::OM->Get('Kernel::Config')->Get("Ticket::Frontend::$Self->{Action}"); 2887 2888 if ( !$Config->{PriorityDefault} ) { 2889 $Priorities{''} = '-'; 2890 } 2891 return \%Priorities; 2892} 2893 2894sub _GetFieldsToUpdate { 2895 my ( $Self, %Param ) = @_; 2896 2897 my @UpdatableFields; 2898 2899 # set the fields that can be updateable via AJAXUpdate 2900 if ( !$Param{OnlyDynamicFields} ) { 2901 @UpdatableFields = qw( 2902 TypeID ServiceID SLAID NewOwnerID NewResponsibleID NewStateID 2903 NewPriorityID 2904 ); 2905 } 2906 2907 # define the dynamic fields to show based on the object type 2908 my $ObjectType = ['Ticket']; 2909 2910 # get config of frontend module 2911 my $Config = $Kernel::OM->Get('Kernel::Config')->Get("Ticket::Frontend::$Self->{Action}"); 2912 2913 # only screens that add notes can modify Article dynamic fields 2914 if ( $Config->{Note} ) { 2915 $ObjectType = [ 'Ticket', 'Article' ]; 2916 } 2917 2918 # get the dynamic fields for this screen 2919 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 2920 Valid => 1, 2921 ObjectType => $ObjectType, 2922 FieldFilter => $Config->{DynamicField} || {}, 2923 ); 2924 2925 # cycle through the activated Dynamic Fields for this screen 2926 DYNAMICFIELD: 2927 for my $DynamicFieldConfig ( @{$DynamicField} ) { 2928 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 2929 2930 my $IsACLReducible = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->HasBehavior( 2931 DynamicFieldConfig => $DynamicFieldConfig, 2932 Behavior => 'IsACLReducible', 2933 ); 2934 next DYNAMICFIELD if !$IsACLReducible; 2935 2936 push @UpdatableFields, 'DynamicField_' . $DynamicFieldConfig->{Name}; 2937 } 2938 2939 return \@UpdatableFields; 2940} 2941 2942sub _GetQuotedReplyBody { 2943 my ( $Self, %Param ) = @_; 2944 2945 # get needed objects 2946 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 2947 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 2948 2949 if ( $LayoutObject->{BrowserRichText} ) { 2950 2951 # rewrap body if exists 2952 if ( $Param{Body} ) { 2953 $Param{Body} =~ s/\t/ /g; 2954 my $Quote = $LayoutObject->Ascii2Html( 2955 Text => $ConfigObject->Get('Ticket::Frontend::Quote') || '', 2956 HTMLResultMode => 1, 2957 ); 2958 if ($Quote) { 2959 2960 # quote text 2961 $Param{Body} = "<blockquote type=\"cite\">$Param{Body}</blockquote>\n"; 2962 2963 # cleanup not compat. tags 2964 $Param{Body} = $LayoutObject->RichTextDocumentCleanup( 2965 String => $Param{Body}, 2966 ); 2967 2968 my $ResponseFormat = $LayoutObject->{LanguageObject} 2969 ->FormatTimeString( $Param{CreateTime}, 'DateFormat', 'NoSeconds' ); 2970 $ResponseFormat .= ' - ' . $Param{From} . ' '; 2971 $ResponseFormat 2972 .= $LayoutObject->{LanguageObject}->Translate('wrote') . ':'; 2973 2974 $Param{Body} = $ResponseFormat . $Param{Body}; 2975 2976 } 2977 else { 2978 $Param{Body} = "<br/>" . $Param{Body}; 2979 2980 if ( $Param{CreateTime} ) { 2981 $Param{Body} = $LayoutObject->{LanguageObject}->Translate('Date') . 2982 ": $Param{CreateTime}<br/>" . $Param{Body}; 2983 } 2984 2985 for (qw(Subject ReplyTo Reply-To Cc To From)) { 2986 if ( $Param{$_} ) { 2987 $Param{Body} = $LayoutObject->{LanguageObject}->Translate($_) . 2988 ": $Param{$_}<br/>" . $Param{Body}; 2989 } 2990 } 2991 2992 my $From = $LayoutObject->Ascii2RichText( 2993 String => $Param{From}, 2994 ); 2995 2996 my $MessageFrom = $LayoutObject->{LanguageObject}->Translate('Message from'); 2997 my $EndMessage = $LayoutObject->{LanguageObject}->Translate('End message'); 2998 2999 $Param{Body} = "<br/>---- $MessageFrom $From ---<br/><br/>" . $Param{Body}; 3000 $Param{Body} .= "<br/>---- $EndMessage ---<br/>"; 3001 } 3002 } 3003 } 3004 else { 3005 3006 # prepare body, subject, ReplyTo ... 3007 # rewrap body if exists 3008 if ( $Param{Body} ) { 3009 $Param{Body} =~ s/\t/ /g; 3010 my $Quote = $ConfigObject->Get('Ticket::Frontend::Quote'); 3011 if ($Quote) { 3012 $Param{Body} =~ s/\n/\n$Quote /g; 3013 $Param{Body} = "\n$Quote " . $Param{Body}; 3014 3015 my $ResponseFormat = $LayoutObject->{LanguageObject} 3016 ->FormatTimeString( $Param{CreateTime}, 'DateFormat', 'NoSeconds' ); 3017 $ResponseFormat .= ' - ' . $Param{From} . ' '; 3018 $ResponseFormat 3019 .= $LayoutObject->{LanguageObject}->Translate('wrote') . ":\n"; 3020 3021 $Param{Body} = $ResponseFormat . $Param{Body}; 3022 } 3023 else { 3024 $Param{Body} = "\n" . $Param{Body}; 3025 if ( $Param{CreateTime} ) { 3026 $Param{Body} = $LayoutObject->{LanguageObject}->Translate('Date') . 3027 ": $Param{CreateTime}\n" . $Param{Body}; 3028 } 3029 3030 for (qw(Subject ReplyTo Reply-To Cc To From)) { 3031 if ( $Param{$_} ) { 3032 $Param{Body} = $LayoutObject->{LanguageObject}->Translate($_) . 3033 ": $Param{$_}\n" . $Param{Body}; 3034 } 3035 } 3036 3037 my $MessageFrom = $LayoutObject->{LanguageObject}->Translate('Message from'); 3038 my $EndMessage = $LayoutObject->{LanguageObject}->Translate('End message'); 3039 3040 $Param{Body} = "\n---- $MessageFrom $Param{From} ---\n\n" . $Param{Body}; 3041 $Param{Body} .= "\n---- $EndMessage ---\n"; 3042 } 3043 } 3044 } 3045 3046 return $Param{Body}; 3047} 3048 3049sub _GetStandardTemplates { 3050 my ( $Self, %Param ) = @_; 3051 3052 # get create templates 3053 my %Templates; 3054 3055 # check needed 3056 return \%Templates if !$Param{QueueID} && !$Param{TicketID}; 3057 3058 my $QueueID = $Param{QueueID} || ''; 3059 if ( !$Param{QueueID} && $Param{TicketID} ) { 3060 3061 # get QueueID from the ticket 3062 my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet( 3063 TicketID => $Param{TicketID}, 3064 DynamicFields => 0, 3065 UserID => $Self->{UserID}, 3066 ); 3067 $QueueID = $Ticket{QueueID} || ''; 3068 } 3069 3070 # fetch all std. templates 3071 my %StandardTemplates = $Kernel::OM->Get('Kernel::System::Queue')->QueueStandardTemplateMemberList( 3072 QueueID => $QueueID, 3073 TemplateTypes => 1, 3074 ); 3075 3076 # return empty hash if there are no templates for this screen 3077 return \%Templates if !IsHashRefWithData( $StandardTemplates{Note} ); 3078 3079 # return just the templates for this screen 3080 return $StandardTemplates{Note}; 3081} 3082 3083sub _GetTypes { 3084 my ( $Self, %Param ) = @_; 3085 3086 # get type 3087 my %Type; 3088 if ( $Param{QueueID} || $Param{TicketID} ) { 3089 %Type = $Kernel::OM->Get('Kernel::System::Ticket')->TicketTypeList( 3090 %Param, 3091 TicketID => $Self->{TicketID}, 3092 Action => $Self->{Action}, 3093 UserID => $Self->{UserID}, 3094 ); 3095 } 3096 return \%Type; 3097} 3098 3099sub _GetQueues { 3100 my ( $Self, %Param ) = @_; 3101 3102 # Get Queues. 3103 my %Queues = $Kernel::OM->Get('Kernel::System::Ticket')->TicketMoveList( 3104 %Param, 3105 TicketID => $Self->{TicketID}, 3106 UserID => $Self->{UserID}, 3107 Action => $Self->{Action}, 3108 Type => 'move_into', 3109 ); 3110 return \%Queues; 3111} 3112 31131; 3114