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::AgentTicketMove; 10 11use strict; 12use warnings; 13 14use Kernel::System::VariableCheck qw(:all); 15use Kernel::Language qw(Translatable); 16 17our $ObjectManagerDisabled = 1; 18 19sub new { 20 my ( $Type, %Param ) = @_; 21 22 # allocate new hash for object 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 # get form id 40 $Self->{FormID} = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'FormID' ); 41 42 # create form id 43 if ( !$Self->{FormID} ) { 44 $Self->{FormID} = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDCreate(); 45 } 46 47 return $Self; 48} 49 50sub Run { 51 my ( $Self, %Param ) = @_; 52 53 # get needed objects 54 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 55 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 56 57 # check needed stuff 58 for my $Needed (qw(TicketID)) { 59 if ( !$Self->{$Needed} ) { 60 return $LayoutObject->ErrorScreen( 61 Message => $LayoutObject->{LanguageObject}->Translate( 'Need %s!', $Needed ), 62 ); 63 } 64 } 65 66 # check permissions 67 my $Access = $TicketObject->TicketPermission( 68 Type => 'move', 69 TicketID => $Self->{TicketID}, 70 UserID => $Self->{UserID} 71 ); 72 73 # error screen, don't show ticket 74 if ( !$Access ) { 75 return $LayoutObject->NoPermission( 76 Message => Translatable("You need move permissions!"), 77 WithHeader => 'yes', 78 ); 79 } 80 81 # get ACL restrictions 82 my %PossibleActions = ( 1 => $Self->{Action} ); 83 84 my $ACL = $TicketObject->TicketAcl( 85 Data => \%PossibleActions, 86 Action => $Self->{Action}, 87 TicketID => $Self->{TicketID}, 88 ReturnType => 'Action', 89 ReturnSubType => '-', 90 UserID => $Self->{UserID}, 91 ); 92 my %AclAction = $TicketObject->TicketAclActionData(); 93 94 # check if ACL restrictions exist 95 if ( $ACL || IsHashRefWithData( \%AclAction ) ) { 96 97 my %AclActionLookup = reverse %AclAction; 98 99 # show error screen if ACL prohibits this action 100 if ( !$AclActionLookup{ $Self->{Action} } ) { 101 return $LayoutObject->NoPermission( WithHeader => 'yes' ); 102 } 103 } 104 105 # Check for failed draft loading request. 106 if ( 107 $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'LoadFormDraft' ) 108 && !$Self->{LoadedFormDraftID} 109 ) 110 { 111 return $LayoutObject->ErrorScreen( 112 Message => Translatable('Loading draft failed!'), 113 Comment => Translatable('Please contact the administrator.'), 114 ); 115 } 116 117 # check if ticket is locked 118 if ( $TicketObject->TicketLockGet( TicketID => $Self->{TicketID} ) ) { 119 my $AccessOk = $TicketObject->OwnerCheck( 120 TicketID => $Self->{TicketID}, 121 OwnerID => $Self->{UserID}, 122 ); 123 if ( !$AccessOk ) { 124 my $Output = $LayoutObject->Header( 125 Type => 'Small', 126 BodyClass => 'Popup', 127 ); 128 $Output .= $LayoutObject->Warning( 129 Message => Translatable('Sorry, you need to be the ticket owner to perform this action.'), 130 Comment => Translatable('Please change the owner first.'), 131 ); 132 133 # show back link 134 $LayoutObject->Block( 135 Name => 'TicketBack', 136 Data => { %Param, TicketID => $Self->{TicketID} }, 137 ); 138 139 $Output .= $LayoutObject->Footer( 140 Type => 'Small', 141 ); 142 return $Output; 143 } 144 } 145 146 # ticket attributes 147 my %Ticket = $TicketObject->TicketGet( 148 TicketID => $Self->{TicketID}, 149 DynamicFields => 1, 150 ); 151 152 # get param object 153 my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); 154 155 # get params 156 my %GetParam; 157 for my $Parameter ( 158 qw(Subject Body 159 NewUserID NewStateID NewPriorityID 160 OwnerAll NoSubmit DestQueueID DestQueue 161 StandardTemplateID CreateArticle FormDraftID Title 162 ) 163 ) 164 { 165 $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter ) || ''; 166 } 167 for my $Parameter (qw(Year Month Day Hour Minute TimeUnits)) { 168 $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter ); 169 } 170 171 # ACL compatibility translations 172 my %ACLCompatGetParam; 173 $ACLCompatGetParam{NewOwnerID} = $GetParam{NewUserID}; 174 $ACLCompatGetParam{QueueID} = $GetParam{DestQueueID}; 175 $ACLCompatGetParam{Queue} = $GetParam{DestQueue}; 176 177 # get Dynamic fields form ParamObject 178 my %DynamicFieldValues; 179 180 # define the dynamic fields to show based on the object type 181 my $ObjectType = ['Ticket']; 182 183 # get config object 184 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 185 186 # get config for frontend module 187 my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}"); 188 189 # only screens that add notes can modify Article dynamic fields 190 if ( $Config->{Note} ) { 191 $ObjectType = [ 'Ticket', 'Article' ]; 192 } 193 194 # get the dynamic fields for this screen 195 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 196 Valid => 1, 197 ObjectType => $ObjectType, 198 FieldFilter => $Config->{DynamicField} || {}, 199 ); 200 201 # get dynamic field backend object 202 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 203 204 # cycle trough the activated Dynamic Fields for this screen 205 DYNAMICFIELD: 206 for my $DynamicFieldConfig ( @{$DynamicField} ) { 207 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 208 209 # extract the dynamic field value from the web request 210 $DynamicFieldValues{ $DynamicFieldConfig->{Name} } = 211 $DynamicFieldBackendObject->EditFieldValueGet( 212 DynamicFieldConfig => $DynamicFieldConfig, 213 ParamObject => $ParamObject, 214 LayoutObject => $LayoutObject, 215 ); 216 } 217 218 # convert dynamic field values into a structure for ACLs 219 my %DynamicFieldACLParameters; 220 DYNAMICFIELD: 221 for my $DynamicFieldItem ( sort keys %DynamicFieldValues ) { 222 next DYNAMICFIELD if !$DynamicFieldItem; 223 next DYNAMICFIELD if !defined $DynamicFieldValues{$DynamicFieldItem}; 224 225 $DynamicFieldACLParameters{ 'DynamicField_' . $DynamicFieldItem } = $DynamicFieldValues{$DynamicFieldItem}; 226 } 227 $GetParam{DynamicField} = \%DynamicFieldACLParameters; 228 229 # transform pending time, time stamp based on user time zone 230 if ( 231 defined $GetParam{Year} 232 && defined $GetParam{Month} 233 && defined $GetParam{Day} 234 && defined $GetParam{Hour} 235 && defined $GetParam{Minute} 236 ) 237 { 238 %GetParam = $LayoutObject->TransformDateSelection( 239 %GetParam, 240 ); 241 } 242 243 # rewrap body if no rich text is used 244 if ( $GetParam{Body} && !$LayoutObject->{BrowserRichText} ) { 245 $GetParam{Body} = $LayoutObject->WrapPlainText( 246 MaxCharacters => $ConfigObject->Get('Ticket::Frontend::TextAreaNote'), 247 PlainText => $GetParam{Body}, 248 ); 249 } 250 251 # error handling 252 my %Error; 253 254 # distinguish between action concerning attachments and the move action 255 my $IsUpload = 0; 256 257 # Get and validate draft action. 258 my $FormDraftAction = $ParamObject->GetParam( Param => 'FormDraftAction' ); 259 if ( $FormDraftAction && !$Config->{FormDraft} ) { 260 return $LayoutObject->ErrorScreen( 261 Message => Translatable('FormDraft functionality disabled!'), 262 Comment => Translatable('Please contact the administrator.'), 263 ); 264 } 265 266 my %FormDraftResponse; 267 268 # Check draft name. 269 if ( 270 $FormDraftAction 271 && ( $FormDraftAction eq 'Add' || $FormDraftAction eq 'Update' ) 272 ) 273 { 274 my $Title = $ParamObject->GetParam( Param => 'FormDraftTitle' ); 275 276 # A draft name is required. 277 if ( !$Title ) { 278 279 %FormDraftResponse = ( 280 Success => 0, 281 ErrorMessage => $Kernel::OM->Get('Kernel::Language')->Translate("Draft name is required!"), 282 ); 283 } 284 285 # Chosen draft name must be unique. 286 else { 287 my $FormDraftList = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftListGet( 288 ObjectType => 'Ticket', 289 ObjectID => $Self->{TicketID}, 290 Action => $Self->{Action}, 291 UserID => $Self->{UserID}, 292 ); 293 DRAFT: 294 for my $FormDraft ( @{$FormDraftList} ) { 295 296 # No existing draft with same name. 297 next DRAFT if $Title ne $FormDraft->{Title}; 298 299 # Same name for update on existing draft. 300 if ( 301 $GetParam{FormDraftID} 302 && $FormDraftAction eq 'Update' 303 && $GetParam{FormDraftID} eq $FormDraft->{FormDraftID} 304 ) 305 { 306 next DRAFT; 307 } 308 309 # Another draft with the chosen name already exists. 310 %FormDraftResponse = ( 311 Success => 0, 312 ErrorMessage => $Kernel::OM->Get('Kernel::Language') 313 ->Translate( "FormDraft name %s is already in use!", $Title ), 314 ); 315 $IsUpload = 1; 316 last DRAFT; 317 } 318 } 319 } 320 321 # Perform draft action instead of saving form data in ticket/article. 322 if ( $FormDraftAction && !%FormDraftResponse ) { 323 324 # Reset FormDraftID to prevent updating existing draft. 325 if ( $FormDraftAction eq 'Add' && $GetParam{FormDraftID} ) { 326 $ParamObject->{Query}->param( 327 -name => 'FormDraftID', 328 -value => '', 329 ); 330 } 331 332 my $FormDraftActionOk; 333 if ( 334 $FormDraftAction eq 'Add' 335 || 336 ( $FormDraftAction eq 'Update' && $GetParam{FormDraftID} ) 337 ) 338 { 339 $FormDraftActionOk = $ParamObject->SaveFormDraft( 340 UserID => $Self->{UserID}, 341 ObjectType => 'Ticket', 342 ObjectID => $Self->{TicketID}, 343 OverrideParams => { 344 NoSubmit => 1, 345 TicketUnlock => undef, 346 }, 347 ); 348 } 349 elsif ( $FormDraftAction eq 'Delete' && $GetParam{FormDraftID} ) { 350 $FormDraftActionOk = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftDelete( 351 FormDraftID => $GetParam{FormDraftID}, 352 UserID => $Self->{UserID}, 353 ); 354 } 355 356 if ($FormDraftActionOk) { 357 $FormDraftResponse{Success} = 1; 358 } 359 else { 360 %FormDraftResponse = ( 361 Success => 0, 362 ErrorMessage => 'Could not perform requested draft action!', 363 ); 364 } 365 } 366 367 if (%FormDraftResponse) { 368 369 # build JSON output 370 my $JSON = $LayoutObject->JSONEncode( 371 Data => \%FormDraftResponse, 372 ); 373 374 # send JSON response 375 return $LayoutObject->Attachment( 376 ContentType => 'application/json; charset=' . $LayoutObject->{Charset}, 377 Content => $JSON, 378 Type => 'inline', 379 NoCache => 1, 380 ); 381 } 382 383 # DestQueueID lookup 384 if ( !$GetParam{DestQueueID} && $GetParam{DestQueue} ) { 385 $GetParam{DestQueueID} 386 = $Kernel::OM->Get('Kernel::System::Queue')->QueueLookup( Queue => $GetParam{DestQueue} ); 387 } 388 if ( !$GetParam{DestQueueID} ) { 389 $Error{DestQueue} = 1; 390 } 391 392 # Check if destination queue is restricted by ACL. 393 my $DestQueues = $Self->_GetQueues( 394 %GetParam, 395 %ACLCompatGetParam, 396 TicketID => $Self->{TicketID}, 397 ); 398 if ( $GetParam{DestQueueID} && !exists $DestQueues->{ $GetParam{DestQueueID} } ) { 399 return $LayoutObject->NoPermission( WithHeader => 'yes' ); 400 } 401 402 # do not submit 403 if ( $GetParam{NoSubmit} ) { 404 $Error{NoSubmit} = 1; 405 } 406 407 # get upload cache object 408 my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache'); 409 410 # Check if TreeView list type is activated. 411 my $TreeView = 0; 412 if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) { 413 $TreeView = 1; 414 } 415 416 # Ajax update 417 if ( $Self->{Subaction} eq 'AJAXUpdate' ) { 418 my $ElementChanged = $ParamObject->GetParam( Param => 'ElementChanged' ) || ''; 419 420 my $NewUsers = $Self->_GetUsers( 421 %GetParam, 422 %ACLCompatGetParam, 423 QueueID => $GetParam{DestQueueID}, 424 AllUsers => $GetParam{OwnerAll}, 425 ); 426 my $NextStates = $Self->_GetNextStates( 427 %GetParam, 428 %ACLCompatGetParam, 429 TicketID => $Self->{TicketID}, 430 QueueID => $GetParam{DestQueueID} || $Ticket{QueueID}, 431 ); 432 my $NextPriorities = $Self->_GetPriorities( 433 %GetParam, 434 %ACLCompatGetParam, 435 TicketID => $Self->{TicketID}, 436 QueueID => $GetParam{DestQueueID} || $Ticket{QueueID}, 437 ); 438 439 # update Dynamc Fields Possible Values via AJAX 440 my @DynamicFieldAJAX; 441 442 # cycle trough the activated Dynamic Fields for this screen 443 DYNAMICFIELD: 444 for my $DynamicFieldConfig ( @{$DynamicField} ) { 445 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 446 447 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 448 DynamicFieldConfig => $DynamicFieldConfig, 449 Behavior => 'IsACLReducible', 450 ); 451 next DYNAMICFIELD if !$IsACLReducible; 452 453 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 454 DynamicFieldConfig => $DynamicFieldConfig, 455 ); 456 457 # convert possible values key => value to key => key for ACLs using a Hash slice 458 my %AclData = %{$PossibleValues}; 459 @AclData{ keys %AclData } = keys %AclData; 460 461 # set possible values filter from ACLs 462 my $ACL = $TicketObject->TicketAcl( 463 %GetParam, 464 %ACLCompatGetParam, 465 Action => $Self->{Action}, 466 TicketID => $Self->{TicketID}, 467 QueueID => $GetParam{DestQueueID} || 0, 468 ReturnType => 'Ticket', 469 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 470 Data => \%AclData, 471 UserID => $Self->{UserID}, 472 ); 473 if ($ACL) { 474 my %Filter = $TicketObject->TicketAclData(); 475 476 # convert Filer key => key back to key => value using map 477 %{$PossibleValues} = map { $_ => $PossibleValues->{$_} } keys %Filter; 478 } 479 480 my $DataValues = $DynamicFieldBackendObject->BuildSelectionDataGet( 481 DynamicFieldConfig => $DynamicFieldConfig, 482 PossibleValues => $PossibleValues, 483 Value => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 484 ) || $PossibleValues; 485 486 # add dynamic field to the list of fields to update 487 push( 488 @DynamicFieldAJAX, 489 { 490 Name => 'DynamicField_' . $DynamicFieldConfig->{Name}, 491 Data => $DataValues, 492 SelectedID => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 493 Translation => $DynamicFieldConfig->{Config}->{TranslatableValues} || 0, 494 Max => 100, 495 } 496 ); 497 } 498 499 my $StandardTemplates = $Self->_GetStandardTemplates( 500 %GetParam, 501 QueueID => $GetParam{DestQueueID} || '', 502 TicketID => $Self->{TicketID}, 503 ); 504 505 my @TemplateAJAX; 506 507 # update ticket body and attachments if needed. 508 if ( $ElementChanged eq 'StandardTemplateID' ) { 509 my @TicketAttachments; 510 my $TemplateText; 511 512 # remove all attachments from the Upload cache 513 my $RemoveSuccess = $UploadCacheObject->FormIDRemove( 514 FormID => $Self->{FormID}, 515 ); 516 if ( !$RemoveSuccess ) { 517 $Kernel::OM->Get('Kernel::System::Log')->Log( 518 Priority => 'error', 519 Message => "Form attachments could not be deleted!", 520 ); 521 } 522 523 # get the template text and set new attachments if a template is selected 524 if ( IsPositiveInteger( $GetParam{StandardTemplateID} ) ) { 525 my $TemplateGenerator = $Kernel::OM->Get('Kernel::System::TemplateGenerator'); 526 527 # set template text, replace smart tags (limited as ticket is not created) 528 $TemplateText = $TemplateGenerator->Template( 529 TemplateID => $GetParam{StandardTemplateID}, 530 TicketID => $Self->{TicketID}, 531 UserID => $Self->{UserID}, 532 ); 533 534 # create StdAttachmentObject 535 my $StdAttachmentObject = $Kernel::OM->Get('Kernel::System::StdAttachment'); 536 537 # add std. attachments to ticket 538 my %AllStdAttachments = $StdAttachmentObject->StdAttachmentStandardTemplateMemberList( 539 StandardTemplateID => $GetParam{StandardTemplateID}, 540 ); 541 for ( sort keys %AllStdAttachments ) { 542 my %AttachmentsData = $StdAttachmentObject->StdAttachmentGet( ID => $_ ); 543 $UploadCacheObject->FormIDAddFile( 544 FormID => $Self->{FormID}, 545 Disposition => 'attachment', 546 %AttachmentsData, 547 ); 548 } 549 550 # send a list of attachments in the upload cache back to the clientside JavaScript 551 # which renders then the list of currently uploaded attachments 552 @TicketAttachments = $UploadCacheObject->FormIDGetAllFilesMeta( 553 FormID => $Self->{FormID}, 554 ); 555 556 for my $Attachment (@TicketAttachments) { 557 $Attachment->{Filesize} = $LayoutObject->HumanReadableDataSize( 558 Size => $Attachment->{Filesize}, 559 ); 560 } 561 } 562 563 @TemplateAJAX = ( 564 { 565 Name => 'UseTemplateNote', 566 Data => '0', 567 }, 568 { 569 Name => 'RichText', 570 Data => $TemplateText || '', 571 }, 572 { 573 Name => 'TicketAttachments', 574 Data => \@TicketAttachments, 575 KeepData => 1, 576 }, 577 ); 578 } 579 580 my $JSON = $LayoutObject->BuildSelectionJSON( 581 [ 582 { 583 Name => 'NewUserID', 584 Data => $NewUsers, 585 SelectedID => $GetParam{NewUserID}, 586 Translation => 0, 587 PossibleNone => 1, 588 Max => 100, 589 }, 590 { 591 Name => 'NewStateID', 592 Data => $NextStates, 593 SelectedID => $GetParam{NewStateID}, 594 Translation => 1, 595 PossibleNone => 1, 596 Max => 100, 597 }, 598 { 599 Name => 'NewPriorityID', 600 Data => $NextPriorities, 601 SelectedID => $GetParam{NewPriorityID}, 602 Translation => 1, 603 PossibleNone => 1, 604 Max => 100, 605 }, 606 { 607 Name => 'StandardTemplateID', 608 Data => $StandardTemplates, 609 SelectedID => $GetParam{StandardTemplateID}, 610 PossibleNone => 1, 611 Translation => 1, 612 Max => 100, 613 }, 614 { 615 Name => 'DestQueueID', 616 Data => $DestQueues, 617 SelectedID => $GetParam{DestQueueID}, 618 PossibleNone => 1, 619 Translation => 0, 620 TreeView => $TreeView, 621 Max => 100, 622 }, 623 @DynamicFieldAJAX, 624 @TemplateAJAX, 625 ], 626 ); 627 return $LayoutObject->Attachment( 628 ContentType => 'application/json; charset=' . $LayoutObject->{Charset}, 629 Content => $JSON, 630 Type => 'inline', 631 NoCache => 1, 632 ); 633 } 634 635 # create HTML strings for all dynamic fields 636 my %DynamicFieldHTML; 637 638 # cycle trough the activated Dynamic Fields for this screen 639 DYNAMICFIELD: 640 for my $DynamicFieldConfig ( @{$DynamicField} ) { 641 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 642 643 my $PossibleValuesFilter; 644 645 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 646 DynamicFieldConfig => $DynamicFieldConfig, 647 Behavior => 'IsACLReducible', 648 ); 649 650 if ($IsACLReducible) { 651 652 # get PossibleValues 653 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 654 DynamicFieldConfig => $DynamicFieldConfig, 655 ); 656 657 # check if field has PossibleValues property in its configuration 658 if ( IsHashRefWithData($PossibleValues) ) { 659 660 # convert possible values key => value to key => key for ACLs using a Hash slice 661 my %AclData = %{$PossibleValues}; 662 @AclData{ keys %AclData } = keys %AclData; 663 664 # set possible values filter from ACLs 665 my $ACL = $TicketObject->TicketAcl( 666 %GetParam, 667 %ACLCompatGetParam, 668 Action => $Self->{Action}, 669 TicketID => $Self->{TicketID}, 670 ReturnType => 'Ticket', 671 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 672 Data => \%AclData, 673 UserID => $Self->{UserID}, 674 ); 675 if ($ACL) { 676 my %Filter = $TicketObject->TicketAclData(); 677 678 # convert Filer key => key back to key => value using map 679 %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} } 680 keys %Filter; 681 } 682 } 683 } 684 685 # to store dynamic field value from database (or undefined) 686 my $Value; 687 688 # only get values for Ticket fields (all screens based on AgentTickeActionCommon 689 # generates a new article, then article fields will be always empty at the beginign) 690 if ( $DynamicFieldConfig->{ObjectType} eq 'Ticket' ) { 691 692 # get value stored on the database from Ticket 693 $Value = $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} }; 694 } 695 696 # get field html 697 $DynamicFieldHTML{ $DynamicFieldConfig->{Name} } = 698 $DynamicFieldBackendObject->EditFieldRender( 699 DynamicFieldConfig => $DynamicFieldConfig, 700 PossibleValuesFilter => $PossibleValuesFilter, 701 Value => $Value, 702 Mandatory => 703 $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2, 704 LayoutObject => $LayoutObject, 705 ParamObject => $ParamObject, 706 AJAXUpdate => 1, 707 UpdatableFields => $Self->_GetFieldsToUpdate(), 708 ); 709 } 710 711 # get state object 712 my $StateObject = $Kernel::OM->Get('Kernel::System::State'); 713 714 # move action 715 if ( $Self->{Subaction} eq 'MoveTicket' ) { 716 717 # challenge token check for write action 718 $LayoutObject->ChallengeTokenCheck(); 719 720 if ( $GetParam{DestQueueID} eq '' ) { 721 $Error{'DestQueueIDInvalid'} = 'ServerError'; 722 } 723 724 # check time units 725 if ( 726 $ConfigObject->Get('Ticket::Frontend::AccountTime') 727 && $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 728 && $GetParam{TimeUnits} eq '' 729 && $Config->{Note} 730 ) 731 { 732 $Error{'TimeUnitsInvalid'} = ' ServerError'; 733 } 734 735 # check pending time 736 if ( $GetParam{NewStateID} ) { 737 my %StateData = $StateObject->StateGet( 738 ID => $GetParam{NewStateID}, 739 ); 740 741 # check state type 742 if ( $StateData{TypeName} =~ /^pending/i ) { 743 744 # check needed stuff 745 for my $TimeParameter (qw(Year Month Day Hour Minute)) { 746 if ( !defined $GetParam{$TimeParameter} ) { 747 $Error{'DateInvalid'} = 'ServerError'; 748 } 749 } 750 751 # create a datetime object based on pending date 752 my $PendingDateTimeObject = $Kernel::OM->Create( 753 'Kernel::System::DateTime', 754 ObjectParams => { 755 %GetParam, 756 Second => 0, 757 }, 758 ); 759 760 # get current system epoch 761 my $CurSystemDateTime = $Kernel::OM->Create('Kernel::System::DateTime'); 762 763 if ( 764 !$PendingDateTimeObject 765 || $PendingDateTimeObject < $CurSystemDateTime 766 ) 767 { 768 $Error{'DateInvalid'} = 'ServerError'; 769 } 770 } 771 } 772 773 if ( $Config->{Note} && $Config->{NoteMandatory} ) { 774 775 # check subject 776 if ( !$GetParam{Subject} ) { 777 $Error{'SubjectInvalid'} = 'ServerError'; 778 } 779 780 # check body 781 if ( !$GetParam{Body} ) { 782 $Error{'BodyInvalid'} = 'ServerError'; 783 } 784 } 785 786 # check mandatory state 787 if ( $Config->{State} && $Config->{StateMandatory} ) { 788 if ( !$GetParam{NewStateID} ) { 789 $Error{'NewStateInvalid'} = 'ServerError'; 790 } 791 } 792 793 # clear DynamicFieldHTML 794 %DynamicFieldHTML = (); 795 796 # cycle trough the activated Dynamic Fields for this screen 797 DYNAMICFIELD: 798 for my $DynamicFieldConfig ( @{$DynamicField} ) { 799 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 800 801 my $PossibleValuesFilter; 802 803 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 804 DynamicFieldConfig => $DynamicFieldConfig, 805 Behavior => 'IsACLReducible', 806 ); 807 808 if ($IsACLReducible) { 809 810 # get PossibleValues 811 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 812 DynamicFieldConfig => $DynamicFieldConfig, 813 ); 814 815 # check if field has PossibleValues property in its configuration 816 if ( IsHashRefWithData($PossibleValues) ) { 817 818 # convert possible values key => value to key => key for ACLs using a Hash slice 819 my %AclData = %{$PossibleValues}; 820 @AclData{ keys %AclData } = keys %AclData; 821 822 # set possible values filter from ACLs 823 my $ACL = $TicketObject->TicketAcl( 824 %GetParam, 825 %ACLCompatGetParam, 826 Action => $Self->{Action}, 827 TicketID => $Self->{TicketID}, 828 ReturnType => 'Ticket', 829 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 830 Data => \%AclData, 831 UserID => $Self->{UserID}, 832 ); 833 if ($ACL) { 834 my %Filter = $TicketObject->TicketAclData(); 835 836 # convert Filer key => key back to key => value using map 837 %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} } 838 keys %Filter; 839 } 840 } 841 } 842 843 my $ValidationResult = $DynamicFieldBackendObject->EditFieldValueValidate( 844 DynamicFieldConfig => $DynamicFieldConfig, 845 PossibleValuesFilter => $PossibleValuesFilter, 846 ParamObject => $ParamObject, 847 Mandatory => 848 $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2, 849 ); 850 851 if ( !IsHashRefWithData($ValidationResult) ) { 852 return $LayoutObject->ErrorScreen( 853 Message => $LayoutObject->{LanguageObject}->Translate( 854 'Could not perform validation on field %s!', 855 $DynamicFieldConfig->{Label}, 856 ), 857 Comment => Translatable('Please contact the administrator.'), 858 ); 859 } 860 861 # propagate validation error to the Error variable to be detected by the frontend 862 if ( $ValidationResult->{ServerError} ) { 863 $Error{ $DynamicFieldConfig->{Name} } = ' ServerError'; 864 } 865 866 # get field html 867 $DynamicFieldHTML{ $DynamicFieldConfig->{Name} } = $DynamicFieldBackendObject->EditFieldRender( 868 DynamicFieldConfig => $DynamicFieldConfig, 869 PossibleValuesFilter => $PossibleValuesFilter, 870 Mandatory => 871 $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2, 872 ServerError => $ValidationResult->{ServerError} || '', 873 ErrorMessage => $ValidationResult->{ErrorMessage} || '', 874 LayoutObject => $LayoutObject, 875 ParamObject => $ParamObject, 876 AJAXUpdate => 1, 877 UpdatableFields => $Self->_GetFieldsToUpdate(), 878 ); 879 } 880 } 881 882 # get params 883 my $TicketUnlock = $ParamObject->GetParam( Param => 'TicketUnlock' ); 884 885 # check errors 886 if (%Error) { 887 888 my $Output = $LayoutObject->Header( 889 Type => 'Small', 890 BodyClass => 'Popup', 891 ); 892 893 # check if lock is required 894 if ( $Config->{RequiredLock} ) { 895 896 # get lock state && write (lock) permissions 897 if ( !$TicketObject->TicketLockGet( TicketID => $Self->{TicketID} ) ) { 898 899 my $Lock = $TicketObject->TicketLockSet( 900 TicketID => $Self->{TicketID}, 901 Lock => 'lock', 902 UserID => $Self->{UserID} 903 ); 904 905 if ($Lock) { 906 907 # Set new owner if ticket owner is different then logged user. 908 if ( $Ticket{OwnerID} != $Self->{UserID} ) { 909 910 # Remember previous owner, which will be used to restore ticket owner on undo action. 911 $Param{PreviousOwner} = $Ticket{OwnerID}; 912 913 $TicketObject->TicketOwnerSet( 914 TicketID => $Self->{TicketID}, 915 UserID => $Self->{UserID}, 916 NewUserID => $Self->{UserID}, 917 ); 918 } 919 920 # Show lock state. 921 $LayoutObject->Block( 922 Name => 'PropertiesLock', 923 Data => { 924 %Param, 925 TicketID => $Self->{TicketID} 926 }, 927 ); 928 $TicketUnlock = 1; 929 } 930 } 931 else { 932 my $AccessOk = $TicketObject->OwnerCheck( 933 TicketID => $Self->{TicketID}, 934 OwnerID => $Self->{UserID}, 935 ); 936 if ( !$AccessOk ) { 937 938 my $Output = $LayoutObject->Header( 939 Type => 'Small', 940 BodyClass => 'Popup', 941 ); 942 $Output .= $LayoutObject->Warning( 943 Message => Translatable('Sorry, you need to be the ticket owner to perform this action.'), 944 Comment => Translatable('Please change the owner first.'), 945 ); 946 947 # show back link 948 $LayoutObject->Block( 949 Name => 'TicketBack', 950 Data => { %Param, TicketID => $Self->{TicketID} }, 951 ); 952 953 $Output .= $LayoutObject->Footer( 954 Type => 'Small', 955 ); 956 return $Output; 957 } 958 959 # show back link 960 $LayoutObject->Block( 961 Name => 'TicketBack', 962 Data => { %Param, TicketID => $Self->{TicketID} }, 963 ); 964 } 965 } 966 else { 967 968 # show back link 969 $LayoutObject->Block( 970 Name => 'TicketBack', 971 Data => { %Param, TicketID => $Self->{TicketID} }, 972 ); 973 } 974 975 # fetch all queues 976 my %MoveQueues = $TicketObject->MoveList( 977 %GetParam, 978 %ACLCompatGetParam, 979 TicketID => $Self->{TicketID}, 980 UserID => $Self->{UserID}, 981 Action => $Self->{Action}, 982 Type => 'move_into', 983 ); 984 985 # get next states 986 my $NextStates = $Self->_GetNextStates( 987 %GetParam, 988 %ACLCompatGetParam, 989 TicketID => $Self->{TicketID}, 990 QueueID => $GetParam{DestQueueID} || $Ticket{QueueID}, 991 ); 992 993 # get next priorities 994 my $NextPriorities = $Self->_GetPriorities( 995 %GetParam, 996 %ACLCompatGetParam, 997 TicketID => $Self->{TicketID}, 998 QueueID => $GetParam{DestQueueID} || $Ticket{QueueID}, 999 ); 1000 1001 # get old owners 1002 my @OldUserInfo = $TicketObject->TicketOwnerList( 1003 %GetParam, 1004 %ACLCompatGetParam, 1005 TicketID => $Self->{TicketID} 1006 ); 1007 1008 # get all attachments meta data 1009 my @Attachments = $UploadCacheObject->FormIDGetAllFilesMeta( 1010 FormID => $Self->{FormID}, 1011 ); 1012 1013 # print change form 1014 $Output .= $Self->AgentMove( 1015 OldUser => \@OldUserInfo, 1016 Attachments => \@Attachments, 1017 MoveQueues => \%MoveQueues, 1018 TicketID => $Self->{TicketID}, 1019 NextStates => $NextStates, 1020 NextPriorities => $NextPriorities, 1021 TicketUnlock => $TicketUnlock, 1022 TimeUnits => $GetParam{TimeUnits}, 1023 FormID => $Self->{FormID}, 1024 1025 %Ticket, 1026 DynamicFieldHTML => \%DynamicFieldHTML, 1027 %GetParam, 1028 %Error, 1029 ); 1030 $Output .= $LayoutObject->Footer( 1031 Type => 'Small', 1032 ); 1033 return $Output; 1034 } 1035 1036 # move ticket (send notification if no new owner is selected) 1037 my $BodyAsText = ''; 1038 if ( $LayoutObject->{BrowserRichText} ) { 1039 $BodyAsText = $LayoutObject->RichText2Ascii( 1040 String => $GetParam{Body} || 0, 1041 ); 1042 } 1043 else { 1044 $BodyAsText = $GetParam{Body} || 0; 1045 } 1046 my $Move = $TicketObject->TicketQueueSet( 1047 QueueID => $GetParam{DestQueueID}, 1048 UserID => $Self->{UserID}, 1049 TicketID => $Self->{TicketID}, 1050 SendNoNotification => $GetParam{NewUserID}, 1051 Comment => $BodyAsText, 1052 ); 1053 if ( !$Move ) { 1054 return $LayoutObject->ErrorScreen(); 1055 } 1056 1057 # set priority 1058 if ( $Config->{Priority} && $GetParam{NewPriorityID} ) { 1059 $TicketObject->TicketPrioritySet( 1060 TicketID => $Self->{TicketID}, 1061 PriorityID => $GetParam{NewPriorityID}, 1062 UserID => $Self->{UserID}, 1063 ); 1064 } 1065 1066 # set state 1067 if ( $Config->{State} && $GetParam{NewStateID} ) { 1068 $TicketObject->TicketStateSet( 1069 TicketID => $Self->{TicketID}, 1070 StateID => $GetParam{NewStateID}, 1071 UserID => $Self->{UserID}, 1072 ); 1073 1074 # unlock the ticket after close 1075 my %StateData = $StateObject->StateGet( 1076 ID => $GetParam{NewStateID}, 1077 ); 1078 1079 # set unlock on close 1080 if ( $StateData{TypeName} =~ /^close/i ) { 1081 $TicketObject->TicketLockSet( 1082 TicketID => $Self->{TicketID}, 1083 Lock => 'unlock', 1084 UserID => $Self->{UserID}, 1085 ); 1086 } 1087 1088 # set pending time on pending state 1089 elsif ( $StateData{TypeName} =~ /^pending/i ) { 1090 1091 # set pending time 1092 $TicketObject->TicketPendingTimeSet( 1093 UserID => $Self->{UserID}, 1094 TicketID => $Self->{TicketID}, 1095 Year => $GetParam{Year}, 1096 Month => $GetParam{Month}, 1097 Day => $GetParam{Day}, 1098 Hour => $GetParam{Hour}, 1099 Minute => $GetParam{Minute}, 1100 ); 1101 } 1102 } 1103 1104 # check if new user is given and send notification 1105 if ( $GetParam{NewUserID} ) { 1106 1107 # lock 1108 $TicketObject->TicketLockSet( 1109 TicketID => $Self->{TicketID}, 1110 Lock => 'lock', 1111 UserID => $Self->{UserID}, 1112 ); 1113 1114 # set owner 1115 $TicketObject->TicketOwnerSet( 1116 TicketID => $Self->{TicketID}, 1117 UserID => $Self->{UserID}, 1118 NewUserID => $GetParam{NewUserID}, 1119 Comment => $BodyAsText, 1120 ); 1121 } 1122 1123 # force unlock if no new owner is set and ticket was unlocked 1124 else { 1125 if ($TicketUnlock) { 1126 $TicketObject->TicketLockSet( 1127 TicketID => $Self->{TicketID}, 1128 Lock => 'unlock', 1129 UserID => $Self->{UserID}, 1130 ); 1131 } 1132 } 1133 1134 # add note (send no notification) 1135 my $ArticleID; 1136 1137 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 1138 1139 if ( 1140 $GetParam{CreateArticle} 1141 && $Config->{Note} 1142 && ( $GetParam{Body} || $GetParam{Subject} ) 1143 ) 1144 { 1145 1146 # get pre-loaded attachments 1147 my @AttachmentData = $UploadCacheObject->FormIDGetAllFilesData( 1148 FormID => $Self->{FormID}, 1149 ); 1150 1151 # get submitted attachment 1152 my %UploadStuff = $ParamObject->GetUploadAll( 1153 Param => 'FileUpload', 1154 ); 1155 if (%UploadStuff) { 1156 push @AttachmentData, \%UploadStuff; 1157 } 1158 1159 my $MimeType = 'text/plain'; 1160 if ( $LayoutObject->{BrowserRichText} ) { 1161 $MimeType = 'text/html'; 1162 1163 # remove unused inline images 1164 my @NewAttachmentData; 1165 ATTACHMENT: 1166 for my $Attachment (@AttachmentData) { 1167 my $ContentID = $Attachment->{ContentID}; 1168 if ( 1169 $ContentID 1170 && ( $Attachment->{ContentType} =~ /image/i ) 1171 && ( $Attachment->{Disposition} eq 'inline' ) 1172 ) 1173 { 1174 my $ContentIDHTMLQuote = $LayoutObject->Ascii2Html( 1175 Text => $ContentID, 1176 ); 1177 next ATTACHMENT 1178 if $GetParam{Body} !~ /(\Q$ContentIDHTMLQuote\E|\Q$ContentID\E)/i; 1179 } 1180 1181 # remember inline images and normal attachments 1182 push @NewAttachmentData, \%{$Attachment}; 1183 } 1184 @AttachmentData = @NewAttachmentData; 1185 1186 # verify HTML document 1187 $GetParam{Body} = $LayoutObject->RichTextDocumentComplete( 1188 String => $GetParam{Body}, 1189 ); 1190 } 1191 1192 my $InternalArticleBackendObject = $ArticleObject->BackendForChannel( ChannelName => 'Internal' ); 1193 $ArticleID = $InternalArticleBackendObject->ArticleCreate( 1194 TicketID => $Self->{TicketID}, 1195 IsVisibleForCustomer => 0, 1196 SenderType => 'agent', 1197 From => "\"$Self->{UserFullname}\" <$Self->{UserEmail}>", 1198 Subject => $GetParam{Subject}, 1199 Body => $GetParam{Body}, 1200 MimeType => $MimeType, 1201 Charset => $LayoutObject->{UserCharset}, 1202 UserID => $Self->{UserID}, 1203 HistoryType => 'AddNote', 1204 HistoryComment => '%%Move', 1205 NoAgentNotify => 1, 1206 ); 1207 if ( !$ArticleID ) { 1208 return $LayoutObject->ErrorScreen(); 1209 } 1210 1211 # write attachments 1212 for my $Attachment (@AttachmentData) { 1213 $InternalArticleBackendObject->ArticleWriteAttachment( 1214 %{$Attachment}, 1215 ArticleID => $ArticleID, 1216 UserID => $Self->{UserID}, 1217 ); 1218 } 1219 1220 # remove pre-submitted attachments 1221 $UploadCacheObject->FormIDRemove( FormID => $Self->{FormID} ); 1222 } 1223 1224 # only set the dynamic fields if the new window was displayed (link), otherwise if ticket was 1225 # moved from the dropdown menu (form) in AgentTicketZoom, the value if the dynamic fields will 1226 # be undefined and it will set to empty in the DB, see bug#8481 1227 if ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'link' ) { 1228 1229 # cycle trough the activated Dynamic Fields for this screen 1230 DYNAMICFIELD: 1231 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1232 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1233 1234 # set the object ID (TicketID or ArticleID) depending on the field configration 1235 my $ObjectID = $DynamicFieldConfig->{ObjectType} eq 'Article' ? $ArticleID : $Self->{TicketID}; 1236 1237 # set dynamic field; when ObjectType=Article and no article will be created ignore 1238 # this dynamic field 1239 if ($ObjectID) { 1240 1241 # set the value 1242 $DynamicFieldBackendObject->ValueSet( 1243 DynamicFieldConfig => $DynamicFieldConfig, 1244 ObjectID => $ObjectID, 1245 Value => $DynamicFieldValues{ $DynamicFieldConfig->{Name} }, 1246 UserID => $Self->{UserID}, 1247 ); 1248 } 1249 } 1250 } 1251 1252 # time accounting 1253 if ( $GetParam{TimeUnits} ) { 1254 $TicketObject->TicketAccountTime( 1255 TicketID => $Self->{TicketID}, 1256 ArticleID => $ArticleID, 1257 TimeUnit => $GetParam{TimeUnits}, 1258 UserID => $Self->{UserID}, 1259 ); 1260 } 1261 1262 # If form was called based on a draft, 1263 # delete draft since its content has now been used. 1264 if ( 1265 $GetParam{FormDraftID} 1266 && !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftDelete( 1267 FormDraftID => $GetParam{FormDraftID}, 1268 UserID => $Self->{UserID}, 1269 ) 1270 ) 1271 { 1272 return $LayoutObject->ErrorScreen( 1273 Message => Translatable('Could not delete draft!'), 1274 Comment => Translatable('Please contact the administrator.'), 1275 ); 1276 } 1277 1278 # check permission for redirect 1279 my $AccessNew = $TicketObject->TicketPermission( 1280 Type => 'ro', 1281 TicketID => $Self->{TicketID}, 1282 UserID => $Self->{UserID} 1283 ); 1284 1285 my $NextScreen = $Config->{NextScreen} || ''; 1286 1287 # redirect to last overview if we do not have ro permissions anymore, 1288 # or if SysConfig option is set. 1289 if ( !$AccessNew || $NextScreen eq 'LastScreenOverview' ) { 1290 1291 # Module directly called 1292 if ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'form' ) { 1293 return $LayoutObject->Redirect( OP => $Self->{LastScreenOverview} ); 1294 } 1295 1296 # Module opened in popup 1297 elsif ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'link' ) { 1298 return $LayoutObject->PopupClose( 1299 URL => ( $Self->{LastScreenOverview} || 'Action=AgentDashboard' ), 1300 ); 1301 } 1302 } 1303 1304 # Module directly called 1305 if ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'form' ) { 1306 return $LayoutObject->Redirect( 1307 OP => "Action=AgentTicketZoom;TicketID=$Self->{TicketID}" 1308 . ( $ArticleID ? ";ArticleID=$ArticleID" : '' ), 1309 ); 1310 } 1311 1312 # Module opened in popup 1313 elsif ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'link' ) { 1314 return $LayoutObject->PopupClose( 1315 URL => "Action=AgentTicketZoom;TicketID=$Self->{TicketID}" 1316 . ( $ArticleID ? ";ArticleID=$ArticleID" : '' ), 1317 ); 1318 } 1319} 1320 1321sub AgentMove { 1322 my ( $Self, %Param ) = @_; 1323 1324 $Param{DestQueueIDInvalid} = $Param{DestQueueIDInvalid} || ''; 1325 1326 # get config object 1327 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1328 1329 # get list type 1330 my $TreeView = 0; 1331 if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) { 1332 $TreeView = 1; 1333 } 1334 1335 # get config for frontend module 1336 my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}"); 1337 1338 # get layout object 1339 my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); 1340 1341 my %Data = %{ $Param{MoveQueues} }; 1342 my %MoveQueues = %Data; 1343 my %UsedData; 1344 1345 my $DynamicFieldNames = $Self->_GetFieldsToUpdate( 1346 OnlyDynamicFields => 1 1347 ); 1348 1349 # send data to JS 1350 $LayoutObject->AddJSData( 1351 Key => 'DynamicFieldNames', 1352 Value => $DynamicFieldNames 1353 ); 1354 1355 # build next states string 1356 $Param{NextStatesStrg} = $LayoutObject->BuildSelection( 1357 Data => $Param{NextStates}, 1358 Name => 'NewStateID', 1359 SelectedID => $Param{NewStateID}, 1360 Translation => 1, 1361 PossibleNone => 1, 1362 Class => 'Modernize ' 1363 . ( $Config->{StateMandatory} ? 'Validate_Required ' : '' ) 1364 . ( $Param{NewStateInvalid} || '' ), 1365 ); 1366 1367 # build next priority string 1368 $Param{NextPrioritiesStrg} = $LayoutObject->BuildSelection( 1369 Data => $Param{NextPriorities}, 1370 Name => 'NewPriorityID', 1371 SelectedID => $Param{NewPriorityID}, 1372 Translation => 1, 1373 PossibleNone => 1, 1374 Class => 'Modernize', 1375 ); 1376 1377 # build owner string 1378 $Param{OwnerStrg} = $LayoutObject->BuildSelection( 1379 Data => $Self->_GetUsers( 1380 QueueID => $Param{DestQueueID}, 1381 AllUsers => $Param{OwnerAll}, 1382 ), 1383 Name => 'NewUserID', 1384 SelectedID => $Param{NewUserID}, 1385 Translation => 0, 1386 PossibleNone => 1, 1387 Class => 'Modernize', 1388 Filters => { 1389 OldOwners => { 1390 Name => $LayoutObject->{LanguageObject}->Translate('Previous Owner'), 1391 Values => $Self->_GetOldOwners( 1392 QueueID => $Param{DestQueueID}, 1393 AllUsers => $Param{OwnerAll}, 1394 ), 1395 }, 1396 }, 1397 ); 1398 1399 $LayoutObject->Block( 1400 Name => 'Owner', 1401 Data => \%Param, 1402 ); 1403 1404 # set state 1405 if ( $Config->{State} ) { 1406 $LayoutObject->Block( 1407 Name => 'State', 1408 Data => { 1409 StateMandatory => $Config->{StateMandatory} || 0, 1410 %Param 1411 }, 1412 ); 1413 } 1414 1415 STATE_ID: 1416 for my $StateID ( sort keys %{ $Param{NextStates} } ) { 1417 next STATE_ID if !$StateID; 1418 my %StateData = $Kernel::OM->Get('Kernel::System::State')->StateGet( ID => $StateID ); 1419 if ( $StateData{TypeName} =~ /pending/i ) { 1420 1421 # get used calendar 1422 my $Calendar = $Kernel::OM->Get('Kernel::System::Ticket')->TicketCalendarGet( 1423 QueueID => $Param{QueueID}, 1424 SLAID => $Param{SLAID}, 1425 ); 1426 1427 $Param{DateString} = $LayoutObject->BuildDateSelection( 1428 Format => 'DateInputFormatLong', 1429 YearPeriodPast => 0, 1430 YearPeriodFuture => 5, 1431 DiffTime => $ConfigObject->Get('Ticket::Frontend::PendingDiffTime') 1432 || 0, 1433 %Param, 1434 Class => $Param{DateInvalid} || ' ', 1435 Validate => 1, 1436 ValidateDateInFuture => 1, 1437 Calendar => $Calendar, 1438 ); 1439 $LayoutObject->Block( 1440 Name => 'StatePending', 1441 Data => \%Param, 1442 ); 1443 last STATE_ID; 1444 } 1445 } 1446 1447 # set priority 1448 if ( $Config->{Priority} ) { 1449 $LayoutObject->Block( 1450 Name => 'Priority', 1451 Data => {%Param}, 1452 ); 1453 } 1454 1455 # set move queues 1456 $Param{MoveQueuesStrg} = $LayoutObject->AgentQueueListOption( 1457 Data => { %MoveQueues, '' => '-' }, 1458 Multiple => 0, 1459 Size => 0, 1460 Class => 'Modernize Validate_Required' . ' ' . $Param{DestQueueIDInvalid}, 1461 Name => 'DestQueueID', 1462 SelectedID => $Param{DestQueueID}, 1463 TreeView => $TreeView, 1464 CurrentQueueID => $Param{QueueID}, 1465 OnChangeSubmit => 0, 1466 ); 1467 1468 $LayoutObject->Block( 1469 Name => 'Queue', 1470 Data => {%Param}, 1471 ); 1472 1473 # define the dynamic fields to show based on the object type 1474 my $ObjectType = ['Ticket']; 1475 1476 # only screens that add notes can modify Article dynamic fields 1477 if ( $Config->{Note} ) { 1478 $ObjectType = [ 'Ticket', 'Article' ]; 1479 } 1480 1481 # get the dynamic fields for this screen 1482 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 1483 Valid => 1, 1484 ObjectType => $ObjectType, 1485 FieldFilter => $Config->{DynamicField} || {}, 1486 ); 1487 1488 if ($DynamicField) { 1489 $LayoutObject->Block( 1490 Name => 'WidgetDynamicFields', 1491 ); 1492 } 1493 1494 # Dynamic fields 1495 # cycle trough the activated Dynamic Fields for this screen 1496 DYNAMICFIELD: 1497 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1498 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1499 1500 # skip fields that HTML could not be retrieved 1501 next DYNAMICFIELD if !IsHashRefWithData( 1502 $Param{DynamicFieldHTML}->{ $DynamicFieldConfig->{Name} } 1503 ); 1504 1505 # get the html strings form $Param 1506 my $DynamicFieldHTML = $Param{DynamicFieldHTML}->{ $DynamicFieldConfig->{Name} }; 1507 1508 $LayoutObject->Block( 1509 Name => 'DynamicField', 1510 Data => { 1511 Name => $DynamicFieldConfig->{Name}, 1512 Label => $DynamicFieldHTML->{Label}, 1513 Field => $DynamicFieldHTML->{Field}, 1514 }, 1515 ); 1516 1517 # example of dynamic fields order customization 1518 $LayoutObject->Block( 1519 Name => 'DynamicField_' . $DynamicFieldConfig->{Name}, 1520 Data => { 1521 Name => $DynamicFieldConfig->{Name}, 1522 Label => $DynamicFieldHTML->{Label}, 1523 Field => $DynamicFieldHTML->{Field}, 1524 }, 1525 ); 1526 1527 } 1528 1529 if ( $Config->{Note} ) { 1530 1531 $Param{WidgetStatus} = 'Collapsed'; 1532 1533 if ( 1534 $Config->{NoteMandatory} 1535 || $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 1536 || $Param{IsUpload} 1537 || $Param{CreateArticle} 1538 ) 1539 { 1540 $Param{WidgetStatus} = 'Expanded'; 1541 } 1542 1543 if ( 1544 $Config->{NoteMandatory} 1545 || $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 1546 ) 1547 { 1548 $Param{SubjectRequired} = 'Validate_Required'; 1549 $Param{BodyRequired} = 'Validate_Required'; 1550 } 1551 else { 1552 $Param{SubjectRequired} = 'Validate_DependingRequiredAND Validate_Depending_CreateArticle'; 1553 $Param{BodyRequired} = 'Validate_DependingRequiredAND Validate_Depending_CreateArticle'; 1554 } 1555 1556 $LayoutObject->Block( 1557 Name => 'WidgetArticle', 1558 Data => {%Param}, 1559 ); 1560 1561 # fillup configured default vars 1562 if ( $Param{Body} eq '' && $Config->{Body} ) { 1563 $Param{Body} = $LayoutObject->Output( 1564 Template => $Config->{Body}, 1565 ); 1566 } 1567 1568 if ( $Param{Subject} eq '' && $Config->{Subject} ) { 1569 $Param{Subject} = $LayoutObject->Output( 1570 Template => $Config->{Subject}, 1571 ); 1572 } 1573 1574 $LayoutObject->Block( 1575 Name => 'Note', 1576 Data => {%Param}, 1577 ); 1578 1579 # build text template string 1580 my %StandardTemplates = $Kernel::OM->Get('Kernel::System::StandardTemplate')->StandardTemplateList( 1581 Valid => 1, 1582 Type => 'Note', 1583 ); 1584 1585 my $QueueStandardTemplates = $Self->_GetStandardTemplates( 1586 %Param, 1587 TicketID => $Self->{TicketID} || '', 1588 ); 1589 1590 if ( IsHashRefWithData( \%StandardTemplates ) ) { 1591 $Param{StandardTemplateStrg} = $LayoutObject->BuildSelection( 1592 Data => $QueueStandardTemplates || {}, 1593 Name => 'StandardTemplateID', 1594 SelectedID => $Param{StandardTemplateID} || '', 1595 PossibleNone => 1, 1596 Sort => 'AlphanumericValue', 1597 Translation => 1, 1598 Max => 200, 1599 Class => 'Modernize', 1600 ); 1601 $LayoutObject->Block( 1602 Name => 'StandardTemplate', 1603 Data => {%Param}, 1604 ); 1605 } 1606 1607 # show time accounting box 1608 if ( $ConfigObject->Get('Ticket::Frontend::AccountTime') ) { 1609 if ( $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') ) { 1610 $LayoutObject->Block( 1611 Name => 'TimeUnitsLabelMandatory', 1612 Data => \%Param, 1613 ); 1614 } 1615 else { 1616 $LayoutObject->Block( 1617 Name => 'TimeUnitsLabel', 1618 Data => \%Param, 1619 ); 1620 } 1621 $LayoutObject->Block( 1622 Name => 'TimeUnits', 1623 Data => { 1624 %Param, 1625 TimeUnitsRequired => ( 1626 $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 1627 ? 'Validate_Required' 1628 : '' 1629 ), 1630 } 1631 ); 1632 } 1633 1634 # show attachments 1635 ATTACHMENT: 1636 for my $Attachment ( @{ $Param{Attachments} } ) { 1637 if ( 1638 $Attachment->{ContentID} 1639 && $LayoutObject->{BrowserRichText} 1640 && ( $Attachment->{ContentType} =~ /image/i ) 1641 && ( $Attachment->{Disposition} eq 'inline' ) 1642 ) 1643 { 1644 next ATTACHMENT; 1645 } 1646 1647 push @{ $Param{AttachmentList} }, $Attachment; 1648 } 1649 1650 # add rich text editor 1651 if ( $LayoutObject->{BrowserRichText} ) { 1652 1653 # use height/width defined for this screen 1654 $Param{RichTextHeight} = $Config->{RichTextHeight} || 0; 1655 $Param{RichTextWidth} = $Config->{RichTextWidth} || 0; 1656 1657 # set up rich text editor 1658 $LayoutObject->SetRichTextParameters( 1659 Data => \%Param, 1660 ); 1661 } 1662 1663 if ( 1664 $Config->{NoteMandatory} 1665 || $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') 1666 ) 1667 { 1668 $LayoutObject->Block( 1669 Name => 'SubjectLabelMandatory', 1670 ); 1671 $LayoutObject->Block( 1672 Name => 'RichTextLabelMandatory', 1673 ); 1674 } 1675 else { 1676 $LayoutObject->Block( 1677 Name => 'SubjectLabel', 1678 ); 1679 $LayoutObject->Block( 1680 Name => 'RichTextLabel', 1681 ); 1682 } 1683 } 1684 1685 my $LoadedFormDraft; 1686 if ( $Self->{LoadedFormDraftID} ) { 1687 $LoadedFormDraft = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftGet( 1688 FormDraftID => $Self->{LoadedFormDraftID}, 1689 GetContent => 0, 1690 UserID => $Self->{UserID}, 1691 ); 1692 1693 my @Articles = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleList( 1694 TicketID => $Self->{TicketID}, 1695 OnlyLast => 1, 1696 ); 1697 1698 if (@Articles) { 1699 my $LastArticle = $Articles[0]; 1700 1701 my $LastArticleSystemTime; 1702 if ( $LastArticle->{CreateTime} ) { 1703 my $LastArticleSystemTimeObject = $Kernel::OM->Create( 1704 'Kernel::System::DateTime', 1705 ObjectParams => { 1706 String => $LastArticle->{CreateTime}, 1707 }, 1708 ); 1709 $LastArticleSystemTime = $LastArticleSystemTimeObject->ToEpoch(); 1710 } 1711 1712 my $FormDraftSystemTimeObject = $Kernel::OM->Create( 1713 'Kernel::System::DateTime', 1714 ObjectParams => { 1715 String => $LoadedFormDraft->{ChangeTime}, 1716 }, 1717 ); 1718 my $FormDraftSystemTime = $FormDraftSystemTimeObject->ToEpoch(); 1719 1720 if ( !$LastArticleSystemTime || $FormDraftSystemTime <= $LastArticleSystemTime ) { 1721 $Param{FormDraftOutdated} = 1; 1722 } 1723 } 1724 } 1725 1726 if ( IsHashRefWithData($LoadedFormDraft) ) { 1727 1728 $LoadedFormDraft->{ChangeByName} = $Kernel::OM->Get('Kernel::System::User')->UserName( 1729 UserID => $LoadedFormDraft->{ChangeBy}, 1730 ); 1731 } 1732 1733 return $LayoutObject->Output( 1734 TemplateFile => 'AgentTicketMove', 1735 Data => { 1736 %Param, 1737 FormDraft => $Config->{FormDraft}, 1738 FormDraftID => $Self->{LoadedFormDraftID}, 1739 FormDraftTitle => $LoadedFormDraft ? $LoadedFormDraft->{Title} : '', 1740 FormDraftMeta => $LoadedFormDraft, 1741 }, 1742 ); 1743} 1744 1745sub _GetUsers { 1746 my ( $Self, %Param ) = @_; 1747 1748 # get users 1749 my %ShownUsers; 1750 my %AllGroupsMembers = $Kernel::OM->Get('Kernel::System::User')->UserList( 1751 Type => 'Long', 1752 Valid => 1, 1753 ); 1754 1755 # get ticket object 1756 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 1757 1758 # just show only users with selected custom queue 1759 if ( $Param{QueueID} && !$Param{AllUsers} ) { 1760 my @UserIDs = $TicketObject->GetSubscribedUserIDsByQueueID(%Param); 1761 for my $UserGroupMember ( sort keys %AllGroupsMembers ) { 1762 my $Hit = 0; 1763 for my $UID (@UserIDs) { 1764 if ( $UID eq $UserGroupMember ) { 1765 $Hit = 1; 1766 } 1767 } 1768 if ( !$Hit ) { 1769 delete $AllGroupsMembers{$UserGroupMember}; 1770 } 1771 } 1772 } 1773 1774 # check show users 1775 if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::ChangeOwnerToEveryone') ) { 1776 %ShownUsers = %AllGroupsMembers; 1777 } 1778 1779 # show all users who are rw in the queue group 1780 elsif ( $Param{QueueID} ) { 1781 my $GID = $Kernel::OM->Get('Kernel::System::Queue')->GetQueueGroupID( QueueID => $Param{QueueID} ); 1782 my %MemberList = $Kernel::OM->Get('Kernel::System::Group')->PermissionGroupGet( 1783 GroupID => $GID, 1784 Type => 'owner', 1785 ); 1786 for my $MemberUsers ( sort keys %MemberList ) { 1787 if ( $AllGroupsMembers{$MemberUsers} ) { 1788 $ShownUsers{$MemberUsers} = $AllGroupsMembers{$MemberUsers}; 1789 } 1790 } 1791 } 1792 1793 # workflow 1794 my $ACL = $TicketObject->TicketAcl( 1795 %Param, 1796 Action => $Self->{Action}, 1797 ReturnType => 'Ticket', 1798 ReturnSubType => 'NewOwner', 1799 Data => \%ShownUsers, 1800 UserID => $Self->{UserID}, 1801 ); 1802 1803 return { $TicketObject->TicketAclData() } if $ACL; 1804 1805 return \%ShownUsers; 1806} 1807 1808sub _GetOldOwners { 1809 my ( $Self, %Param ) = @_; 1810 1811 # get ticket object 1812 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 1813 1814 my @OldUserInfo = $TicketObject->TicketOwnerList( TicketID => $Self->{TicketID} ); 1815 my %UserHash; 1816 if (@OldUserInfo) { 1817 my $Counter = 1; 1818 USER: 1819 for my $User ( reverse @OldUserInfo ) { 1820 next USER if $UserHash{ $User->{UserID} }; 1821 $UserHash{ $User->{UserID} } = "$Counter: $User->{UserFullname}"; 1822 $Counter++; 1823 } 1824 } 1825 1826 # workflow 1827 my $ACL = $TicketObject->TicketAcl( 1828 %Param, 1829 Action => $Self->{Action}, 1830 ReturnType => 'Ticket', 1831 ReturnSubType => 'OldOwner', 1832 Data => \%UserHash, 1833 UserID => $Self->{UserID}, 1834 ); 1835 1836 return { $TicketObject->TicketAclData() } if $ACL; 1837 1838 return \%UserHash; 1839} 1840 1841sub _GetPriorities { 1842 my ( $Self, %Param ) = @_; 1843 1844 # get priority 1845 my %Priorities; 1846 if ( $Param{QueueID} || $Param{TicketID} ) { 1847 %Priorities = $Kernel::OM->Get('Kernel::System::Ticket')->TicketPriorityList( 1848 %Param, 1849 Action => $Self->{Action}, 1850 UserID => $Self->{UserID}, 1851 ); 1852 } 1853 return \%Priorities; 1854} 1855 1856sub _GetNextStates { 1857 my ( $Self, %Param ) = @_; 1858 1859 my %NextStates; 1860 if ( $Param{QueueID} || $Param{TicketID} ) { 1861 %NextStates = $Kernel::OM->Get('Kernel::System::Ticket')->TicketStateList( 1862 %Param, 1863 Action => $Self->{Action}, 1864 UserID => $Self->{UserID}, 1865 ); 1866 } 1867 return \%NextStates; 1868} 1869 1870sub _GetFieldsToUpdate { 1871 my ( $Self, %Param ) = @_; 1872 1873 my @UpdatableFields; 1874 1875 # set the fields that can be updatable via AJAXUpdate 1876 if ( !$Param{OnlyDynamicFields} ) { 1877 @UpdatableFields = qw( DestQueueID NewUserID NewStateID NewPriorityID ); 1878 } 1879 1880 # define the dynamic fields to show based on the object type 1881 my $ObjectType = ['Ticket']; 1882 1883 # get config for frontend module 1884 my $Config = $Kernel::OM->Get('Kernel::Config')->Get("Ticket::Frontend::$Self->{Action}"); 1885 1886 # only screens that add notes can modify Article dynamic fields 1887 if ( $Config->{Note} ) { 1888 $ObjectType = [ 'Ticket', 'Article' ]; 1889 } 1890 1891 # get the dynamic fields for this screen 1892 my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 1893 Valid => 1, 1894 ObjectType => $ObjectType, 1895 FieldFilter => $Config->{DynamicField} || {}, 1896 ); 1897 1898 # cycle trough the activated Dynamic Fields for this screen 1899 DYNAMICFIELD: 1900 for my $DynamicFieldConfig ( @{$DynamicField} ) { 1901 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1902 1903 my $IsACLReducible = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->HasBehavior( 1904 DynamicFieldConfig => $DynamicFieldConfig, 1905 Behavior => 'IsACLReducible', 1906 ); 1907 next DYNAMICFIELD if !$IsACLReducible; 1908 1909 push @UpdatableFields, 'DynamicField_' . $DynamicFieldConfig->{Name}; 1910 } 1911 1912 return \@UpdatableFields; 1913} 1914 1915sub _GetStandardTemplates { 1916 my ( $Self, %Param ) = @_; 1917 1918 # get create templates 1919 my %Templates; 1920 1921 # check needed 1922 return \%Templates if !$Param{QueueID} && !$Param{TicketID}; 1923 1924 my $QueueID = $Param{QueueID} || ''; 1925 if ( !$Param{QueueID} && $Param{TicketID} ) { 1926 1927 # get QueueID from the ticket 1928 my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet( 1929 TicketID => $Param{TicketID}, 1930 DynamicFields => 0, 1931 UserID => $Self->{UserID}, 1932 ); 1933 $QueueID = $Ticket{QueueID} || ''; 1934 } 1935 1936 # fetch all std. templates 1937 my %StandardTemplates = $Kernel::OM->Get('Kernel::System::Queue')->QueueStandardTemplateMemberList( 1938 QueueID => $QueueID, 1939 TemplateTypes => 1, 1940 ); 1941 1942 # return empty hash if there are no templates for this screen 1943 return \%Templates if !IsHashRefWithData( $StandardTemplates{Note} ); 1944 1945 # return just the templates for this screen 1946 return $StandardTemplates{Note}; 1947} 1948 1949sub _GetQueues { 1950 my ( $Self, %Param ) = @_; 1951 1952 # Get Queues. 1953 my %Queues = $Kernel::OM->Get('Kernel::System::Ticket')->TicketMoveList( 1954 %Param, 1955 TicketID => $Self->{TicketID}, 1956 UserID => $Self->{UserID}, 1957 Action => $Self->{Action}, 1958 Type => 'move_into', 1959 ); 1960 return \%Queues; 1961} 1962 19631; 1964