1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::System::Ticket; 10 11use strict; 12use warnings; 13 14use File::Path; 15use utf8; 16use Encode (); 17 18use parent qw( 19 Kernel::System::EventHandler 20 Kernel::System::Ticket::TicketSearch 21 Kernel::System::Ticket::TicketACL 22); 23 24use Kernel::Language qw(Translatable); 25use Kernel::System::VariableCheck qw(:all); 26 27our @ObjectDependencies = ( 28 'Kernel::Config', 29 'Kernel::System::Cache', 30 'Kernel::System::Calendar', 31 'Kernel::System::CustomerUser', 32 'Kernel::System::DB', 33 'Kernel::System::DynamicField', 34 'Kernel::System::DynamicField::Backend', 35 'Kernel::System::DynamicFieldValue', 36 'Kernel::System::Email', 37 'Kernel::System::Group', 38 'Kernel::System::HTMLUtils', 39 'Kernel::System::LinkObject', 40 'Kernel::System::Lock', 41 'Kernel::System::Log', 42 'Kernel::System::Main', 43 'Kernel::System::PostMaster::LoopProtection', 44 'Kernel::System::Priority', 45 'Kernel::System::Queue', 46 'Kernel::System::Service', 47 'Kernel::System::SLA', 48 'Kernel::System::State', 49 'Kernel::System::TemplateGenerator', 50 'Kernel::System::DateTime', 51 'Kernel::System::Ticket::Article', 52 'Kernel::System::Type', 53 'Kernel::System::User', 54 'Kernel::System::Valid', 55 'Kernel::Language', 56); 57 58=head1 NAME 59 60Kernel::System::Ticket - Functions to create, modify and delete tickets as well as related helper functions 61 62=head1 SYNOPSIS 63 64Create ticket object 65 66 use Kernel::System::ObjectManager; 67 local $Kernel::OM = Kernel::System::ObjectManager->new(); 68 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 69 70Create a new ticket 71 72 my $TicketID = $TicketObject->TicketCreate( 73 Title => 'Some Ticket Title', 74 Queue => 'Raw', 75 Lock => 'unlock', 76 Priority => '3 normal', 77 State => 'new', 78 CustomerID => '12345', 79 CustomerUser => 'customer@example.com', 80 OwnerID => 1, 81 UserID => 1, 82 ); 83 84Lock the ticket 85 86 my $Success = $TicketObject->TicketLockSet( 87 Lock => 'lock', 88 TicketID => $TicketID, 89 UserID => 1, 90 ); 91 92 93Update the title 94 95 my $Success = $TicketObject->TicketTitleUpdate( 96 Title => 'Some Title', 97 TicketID => $TicketID, 98 UserID => 1, 99 ); 100 101Move ticket to another queue 102 103 my $Success = $TicketObject->TicketQueueSet( 104 Queue => 'Some Queue Name', 105 TicketID => $TicketID, 106 UserID => 1, 107 ); 108 109Set a ticket type 110 111 my $Success = $TicketObject->TicketTypeSet( 112 Type => 'Incident', 113 TicketID => $TicketID, 114 UserID => 1, 115 ); 116 117Assign another customer 118 119 my $Success = $TicketObject->TicketCustomerSet( 120 No => '12345', 121 User => 'customer@company.org', 122 TicketID => $TicketID, 123 UserID => 1, 124 ); 125 126Update the state 127 128 my $Success = $TicketObject->TicketStateSet( 129 State => 'pending reminder', 130 TicketID => $TicketID, 131 UserID => 1, 132 ); 133 134Update pending time (only for pending states) 135 136 my $Success = $TicketObject->TicketPendingTimeSet( 137 String => '2019-08-14 22:05:00', 138 TicketID => $TicketID, 139 UserID => 1, 140 ); 141 142Set a new priority 143 144 my $Success = $TicketObject->TicketPrioritySet( 145 TicketID => $TicketID, 146 Priority => 'low', 147 UserID => 1, 148 ); 149 150Assign to another agent 151 152 my $Success = $TicketObject->TicketOwnerSet( 153 TicketID => $TicketID, 154 NewUserID => 2, 155 UserID => 1, 156 ); 157 158Set a responsible 159 160 my $Success = $TicketObject->TicketResponsibleSet( 161 TicketID => $TicketID, 162 NewUserID => 3, 163 UserID => 1, 164 ); 165 166Add something to the history 167 168 my $Success = $TicketObject->HistoryAdd( 169 Name => 'Some Comment', 170 HistoryType => 'Move', 171 TicketID => $TicketID, 172 CreateUserID => 1, 173 ); 174 175Get the complete ticket history 176 177 my @HistoryLines = $TicketObject->HistoryGet( 178 TicketID => $TicketID, 179 UserID => 1, 180 ); 181 182Get current ticket attributes 183 184 my %Ticket = $TicketObject->TicketGet( 185 TicketID => $TicketID, 186 UserID => 1, 187 ); 188 189Delete the ticket 190 191 my $Success = $TicketObject->TicketDelete( 192 TicketID => $TicketID, 193 UserID => 1, 194 ); 195 196=head1 PUBLIC INTERFACE 197 198=head2 new() 199 200Don't use the constructor directly, use the ObjectManager instead: 201 202 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 203 204=cut 205 206sub new { 207 my ( $Type, %Param ) = @_; 208 209 # allocate new hash for object 210 my $Self = {}; 211 bless( $Self, $Type ); 212 213 # 0=off; 1=on; 214 $Self->{Debug} = $Param{Debug} || 0; 215 216 $Self->{CacheType} = 'Ticket'; 217 $Self->{CacheTTL} = 60 * 60 * 24 * 20; 218 219 # init of event handler 220 $Self->EventHandlerInit( 221 Config => 'Ticket::EventModulePost', 222 ); 223 224 # get needed objects 225 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 226 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 227 228 # load ticket extension modules 229 my $CustomModule = $ConfigObject->Get('Ticket::CustomModule'); 230 if ($CustomModule) { 231 232 my %ModuleList; 233 if ( ref $CustomModule eq 'HASH' ) { 234 %ModuleList = %{$CustomModule}; 235 } 236 else { 237 $ModuleList{Init} = $CustomModule; 238 } 239 240 MODULEKEY: 241 for my $ModuleKey ( sort keys %ModuleList ) { 242 243 my $Module = $ModuleList{$ModuleKey}; 244 245 next MODULEKEY if !$Module; 246 next MODULEKEY if !$MainObject->RequireBaseClass($Module); 247 } 248 } 249 250 return $Self; 251} 252 253=head2 TicketCreateNumber() 254 255creates a new ticket number 256 257 my $TicketNumber = $TicketObject->TicketCreateNumber(); 258 259=cut 260 261sub TicketCreateNumber { 262 my ( $Self, %Param ) = @_; 263 264 my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::NumberGenerator') 265 || 'Kernel::System::Ticket::Number::AutoIncrement'; 266 267 return $Kernel::OM->Get($GeneratorModule)->TicketCreateNumber(%Param); 268} 269 270=head2 GetTNByString() 271 272creates a new ticket number 273 274 my $TicketNumber = $TicketObject->GetTNByString($Subject); 275 276=cut 277 278sub GetTNByString { 279 my ( $Self, $String ) = @_; 280 281 my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::NumberGenerator') 282 || 'Kernel::System::Ticket::Number::AutoIncrement'; 283 284 return $Kernel::OM->Get($GeneratorModule)->GetTNByString($String); 285} 286 287=head2 TicketCheckNumber() 288 289checks if ticket number exists, returns ticket id if number exists. 290 291returns the merged ticket id if ticket was merged. 292only into a depth of maximum 10 merges 293 294 my $TicketID = $TicketObject->TicketCheckNumber( 295 Tn => '200404051004575', 296 ); 297 298=cut 299 300sub TicketCheckNumber { 301 my ( $Self, %Param ) = @_; 302 303 # check needed stuff 304 if ( !$Param{Tn} ) { 305 $Kernel::OM->Get('Kernel::System::Log')->Log( 306 Priority => 'error', 307 Message => 'Need TN!' 308 ); 309 return; 310 } 311 312 # get database object 313 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 314 315 # db query 316 return if !$DBObject->Prepare( 317 SQL => 'SELECT id FROM ticket WHERE tn = ?', 318 Bind => [ \$Param{Tn} ], 319 Limit => 1, 320 ); 321 322 my $TicketID; 323 while ( my @Row = $DBObject->FetchrowArray() ) { 324 $TicketID = $Row[0]; 325 } 326 327 # get main ticket id if ticket has been merged 328 return if !$TicketID; 329 330 # do not check deeper than 10 merges 331 my $Limit = 10; 332 my $Count = 1; 333 MERGELOOP: 334 for ( 1 .. $Limit ) { 335 my %Ticket = $Self->TicketGet( 336 TicketID => $TicketID, 337 DynamicFields => 0, 338 ); 339 340 return $TicketID if $Ticket{StateType} ne 'merged'; 341 342 # get ticket history 343 my @Lines = $Self->HistoryGet( 344 TicketID => $TicketID, 345 UserID => 1, 346 ); 347 348 HISTORYLINE: 349 for my $Data ( reverse @Lines ) { 350 next HISTORYLINE if $Data->{HistoryType} ne 'Merged'; 351 if ( $Data->{Name} =~ /^.*%%\d+?%%(\d+?)$/ ) { 352 $TicketID = $1; 353 $Count++; 354 next MERGELOOP if ( $Count <= $Limit ); 355 356 # returns no found Ticket after 10 deep-merges, so it should create a new one 357 return; 358 } 359 } 360 361 return $TicketID; 362 } 363 return; 364} 365 366=head2 TicketCreate() 367 368creates a new ticket 369 370 my $TicketID = $TicketObject->TicketCreate( 371 Title => 'Some Ticket Title', 372 Queue => 'Raw', # or QueueID => 123, 373 Lock => 'unlock', 374 Priority => '3 normal', # or PriorityID => 2, 375 State => 'new', # or StateID => 5, 376 CustomerID => '123465', 377 CustomerUser => 'customer@example.com', 378 OwnerID => 123, 379 UserID => 123, 380 ); 381 382or 383 384 my $TicketID = $TicketObject->TicketCreate( 385 TN => $TicketObject->TicketCreateNumber(), # optional 386 Title => 'Some Ticket Title', 387 Queue => 'Raw', # or QueueID => 123, 388 Lock => 'unlock', 389 Priority => '3 normal', # or PriorityID => 2, 390 State => 'new', # or StateID => 5, 391 Type => 'Incident', # or TypeID = 1 or Ticket type default (Ticket::Type::Default), not required 392 Service => 'Service A', # or ServiceID => 1, not required 393 SLA => 'SLA A', # or SLAID => 1, not required 394 CustomerID => '123465', 395 CustomerUser => 'customer@example.com', 396 OwnerID => 123, 397 ResponsibleID => 123, # not required 398 ArchiveFlag => 'y', # (y|n) not required 399 UserID => 123, 400 ); 401 402Events: 403 TicketCreate 404 405=cut 406 407sub TicketCreate { 408 my ( $Self, %Param ) = @_; 409 410 # check needed stuff 411 for my $Needed (qw(OwnerID UserID)) { 412 if ( !$Param{$Needed} ) { 413 $Kernel::OM->Get('Kernel::System::Log')->Log( 414 Priority => 'error', 415 Message => "Need $Needed!" 416 ); 417 return; 418 } 419 } 420 421 my $ArchiveFlag = 0; 422 if ( $Param{ArchiveFlag} && $Param{ArchiveFlag} eq 'y' ) { 423 $ArchiveFlag = 1; 424 } 425 426 $Param{ResponsibleID} ||= 1; 427 428 # get type object 429 my $TypeObject = $Kernel::OM->Get('Kernel::System::Type'); 430 431 if ( !$Param{TypeID} && !$Param{Type} ) { 432 433 # get default ticket type 434 my $DefaultTicketType = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Type::Default'); 435 436 # check if default ticket type exists 437 my %AllTicketTypes = reverse $TypeObject->TypeList(); 438 439 if ( $AllTicketTypes{$DefaultTicketType} ) { 440 $Param{Type} = $DefaultTicketType; 441 } 442 else { 443 $Param{TypeID} = 1; 444 } 445 } 446 447 # TypeID/Type lookup! 448 if ( !$Param{TypeID} && $Param{Type} ) { 449 $Param{TypeID} = $TypeObject->TypeLookup( Type => $Param{Type} ); 450 } 451 elsif ( $Param{TypeID} && !$Param{Type} ) { 452 $Param{Type} = $TypeObject->TypeLookup( TypeID => $Param{TypeID} ); 453 } 454 if ( !$Param{TypeID} ) { 455 $Kernel::OM->Get('Kernel::System::Log')->Log( 456 Priority => 'error', 457 Message => "No TypeID for '$Param{Type}'!", 458 ); 459 return; 460 } 461 462 # get queue object 463 my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); 464 465 # QueueID/Queue lookup! 466 if ( !$Param{QueueID} && $Param{Queue} ) { 467 $Param{QueueID} = $QueueObject->QueueLookup( Queue => $Param{Queue} ); 468 } 469 elsif ( !$Param{Queue} ) { 470 $Param{Queue} = $QueueObject->QueueLookup( QueueID => $Param{QueueID} ); 471 } 472 if ( !$Param{QueueID} ) { 473 $Kernel::OM->Get('Kernel::System::Log')->Log( 474 Priority => 'error', 475 Message => "No QueueID for '$Param{Queue}'!", 476 ); 477 return; 478 } 479 480 # get state object 481 my $StateObject = $Kernel::OM->Get('Kernel::System::State'); 482 483 # StateID/State lookup! 484 if ( !$Param{StateID} ) { 485 my %State = $StateObject->StateGet( Name => $Param{State} ); 486 $Param{StateID} = $State{ID}; 487 } 488 elsif ( !$Param{State} ) { 489 my %State = $StateObject->StateGet( ID => $Param{StateID} ); 490 $Param{State} = $State{Name}; 491 } 492 if ( !$Param{StateID} ) { 493 $Kernel::OM->Get('Kernel::System::Log')->Log( 494 Priority => 'error', 495 Message => "No StateID for '$Param{State}'!", 496 ); 497 return; 498 } 499 500 # LockID lookup! 501 if ( !$Param{LockID} && $Param{Lock} ) { 502 503 $Param{LockID} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup( 504 Lock => $Param{Lock}, 505 ); 506 } 507 if ( !$Param{LockID} && !$Param{Lock} ) { 508 509 $Kernel::OM->Get('Kernel::System::Log')->Log( 510 Priority => 'error', 511 Message => 'No LockID and no LockType!', 512 ); 513 return; 514 } 515 516 # get priority object 517 my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority'); 518 519 # PriorityID/Priority lookup! 520 if ( !$Param{PriorityID} && $Param{Priority} ) { 521 $Param{PriorityID} = $PriorityObject->PriorityLookup( 522 Priority => $Param{Priority}, 523 ); 524 } 525 elsif ( $Param{PriorityID} && !$Param{Priority} ) { 526 $Param{Priority} = $PriorityObject->PriorityLookup( 527 PriorityID => $Param{PriorityID}, 528 ); 529 } 530 if ( !$Param{PriorityID} ) { 531 $Kernel::OM->Get('Kernel::System::Log')->Log( 532 Priority => 'error', 533 Message => 'No PriorityID (invalid Priority Name?)!', 534 ); 535 return; 536 } 537 538 # get service object 539 my $ServiceObject = $Kernel::OM->Get('Kernel::System::Service'); 540 541 # ServiceID/Service lookup! 542 if ( !$Param{ServiceID} && $Param{Service} ) { 543 $Param{ServiceID} = $ServiceObject->ServiceLookup( 544 Name => $Param{Service}, 545 ); 546 } 547 elsif ( $Param{ServiceID} && !$Param{Service} ) { 548 $Param{Service} = $ServiceObject->ServiceLookup( 549 ServiceID => $Param{ServiceID}, 550 ); 551 } 552 553 # get sla object 554 my $SLAObject = $Kernel::OM->Get('Kernel::System::SLA'); 555 556 # SLAID/SLA lookup! 557 if ( !$Param{SLAID} && $Param{SLA} ) { 558 $Param{SLAID} = $SLAObject->SLALookup( Name => $Param{SLA} ); 559 } 560 elsif ( $Param{SLAID} && !$Param{SLA} ) { 561 $Param{SLA} = $SLAObject->SLALookup( SLAID => $Param{SLAID} ); 562 } 563 564 # create ticket number if none is given 565 if ( !$Param{TN} ) { 566 $Param{TN} = $Self->TicketCreateNumber(); 567 } 568 569 # check ticket title 570 if ( !defined $Param{Title} ) { 571 $Param{Title} = ''; 572 } 573 574 # substitute title if needed 575 else { 576 $Param{Title} = substr( $Param{Title}, 0, 255 ); 577 } 578 579 # check database undef/NULL (set value to undef/NULL to prevent database errors) 580 $Param{ServiceID} ||= undef; 581 $Param{SLAID} ||= undef; 582 583 # create db record 584 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 585 SQL => ' 586 INSERT INTO ticket (tn, title, type_id, queue_id, ticket_lock_id, 587 user_id, responsible_user_id, ticket_priority_id, ticket_state_id, 588 escalation_time, escalation_update_time, escalation_response_time, 589 escalation_solution_time, timeout, service_id, sla_id, until_time, 590 archive_flag, create_time, create_by, change_time, change_by) 591 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, 0, 0, ?, ?, 0, ?, 592 current_timestamp, ?, current_timestamp, ?)', 593 Bind => [ 594 \$Param{TN}, \$Param{Title}, \$Param{TypeID}, \$Param{QueueID}, 595 \$Param{LockID}, \$Param{OwnerID}, \$Param{ResponsibleID}, 596 \$Param{PriorityID}, \$Param{StateID}, \$Param{ServiceID}, 597 \$Param{SLAID}, \$ArchiveFlag, \$Param{UserID}, \$Param{UserID}, 598 ], 599 ); 600 601 # get ticket id 602 my $TicketID = $Self->TicketIDLookup( 603 TicketNumber => $Param{TN}, 604 UserID => $Param{UserID}, 605 ); 606 607 # add history entry 608 $Self->HistoryAdd( 609 TicketID => $TicketID, 610 QueueID => $Param{QueueID}, 611 HistoryType => 'NewTicket', 612 Name => "\%\%$Param{TN}\%\%$Param{Queue}\%\%$Param{Priority}\%\%$Param{State}\%\%$TicketID", 613 CreateUserID => $Param{UserID}, 614 ); 615 616 if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service') ) { 617 618 # history insert for service so that initial values can be seen 619 my $HistoryService = $Param{Service} || 'NULL'; 620 my $HistoryServiceID = $Param{ServiceID} || ''; 621 $Self->HistoryAdd( 622 TicketID => $TicketID, 623 HistoryType => 'ServiceUpdate', 624 Name => "\%\%$HistoryService\%\%$HistoryServiceID\%\%NULL\%\%", 625 CreateUserID => $Param{UserID}, 626 ); 627 628 # history insert for SLA 629 my $HistorySLA = $Param{SLA} || 'NULL'; 630 my $HistorySLAID = $Param{SLAID} || ''; 631 $Self->HistoryAdd( 632 TicketID => $TicketID, 633 HistoryType => 'SLAUpdate', 634 Name => "\%\%$HistorySLA\%\%$HistorySLAID\%\%NULL\%\%", 635 CreateUserID => $Param{UserID}, 636 ); 637 } 638 639 if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Type') ) { 640 641 # Insert history record for ticket type, so that initial value can be seen. 642 # Please see bug#12702 for more information. 643 $Self->HistoryAdd( 644 TicketID => $TicketID, 645 HistoryType => 'TypeUpdate', 646 Name => "\%\%$Param{Type}\%\%$Param{TypeID}", 647 CreateUserID => $Param{UserID}, 648 ); 649 } 650 651 # set customer data if given 652 if ( $Param{CustomerNo} || $Param{CustomerID} || $Param{CustomerUser} ) { 653 $Self->TicketCustomerSet( 654 TicketID => $TicketID, 655 No => $Param{CustomerNo} || $Param{CustomerID} || '', 656 User => $Param{CustomerUser} || '', 657 UserID => $Param{UserID}, 658 ); 659 } 660 661 # update ticket view index 662 $Self->TicketAcceleratorAdd( TicketID => $TicketID ); 663 664 # log ticket creation 665 $Kernel::OM->Get('Kernel::System::Log')->Log( 666 Priority => 'info', 667 Message => "New Ticket [$Param{TN}/" . substr( $Param{Title}, 0, 15 ) . "] created " 668 . "(TicketID=$TicketID,Queue=$Param{Queue},Priority=$Param{Priority},State=$Param{State})", 669 ); 670 671 # trigger event 672 $Self->EventHandler( 673 Event => 'TicketCreate', 674 Data => { 675 TicketID => $TicketID, 676 }, 677 UserID => $Param{UserID}, 678 ); 679 680 return $TicketID; 681} 682 683=head2 TicketDelete() 684 685deletes a ticket with articles from storage 686 687 my $Success = $TicketObject->TicketDelete( 688 TicketID => 123, 689 UserID => 123, 690 ); 691 692Events: 693 TicketDelete 694 695=cut 696 697sub TicketDelete { 698 my ( $Self, %Param ) = @_; 699 700 # check needed stuff 701 for my $Needed (qw(TicketID UserID)) { 702 if ( !$Param{$Needed} ) { 703 $Kernel::OM->Get('Kernel::System::Log')->Log( 704 Priority => 'error', 705 Message => "Need $Needed!", 706 ); 707 return; 708 } 709 } 710 711 # Delete dynamic field values for this ticket. 712 $Kernel::OM->Get('Kernel::System::DynamicFieldValue')->ObjectValuesDelete( 713 ObjectType => 'Ticket', 714 ObjectID => $Param{TicketID}, 715 UserID => $Param{UserID}, 716 ); 717 718 # clear ticket cache 719 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 720 721 # delete ticket links 722 $Kernel::OM->Get('Kernel::System::LinkObject')->LinkDeleteAll( 723 Object => 'Ticket', 724 Key => $Param{TicketID}, 725 UserID => $Param{UserID}, 726 ); 727 728 # update ticket index 729 return if !$Self->TicketAcceleratorDelete(%Param); 730 731 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 732 733 # delete ticket entries from article search index. 734 return if !$ArticleObject->ArticleSearchIndexDelete( 735 TicketID => $Param{TicketID}, 736 UserID => $Param{UserID}, 737 ); 738 739 # get database object 740 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 741 742 # remove ticket watcher 743 return if !$DBObject->Do( 744 SQL => 'DELETE FROM ticket_watcher WHERE ticket_id = ?', 745 Bind => [ \$Param{TicketID} ], 746 ); 747 748 # delete ticket flags 749 return if !$DBObject->Do( 750 SQL => 'DELETE FROM ticket_flag WHERE ticket_id = ?', 751 Bind => [ \$Param{TicketID} ], 752 ); 753 754 # delete ticket flag cache 755 $Kernel::OM->Get('Kernel::System::Cache')->Delete( 756 Type => $Self->{CacheType}, 757 Key => 'TicketFlag::' . $Param{TicketID}, 758 ); 759 760 # Delete calendar appointments linked to this ticket. 761 # Please see bug#13642 for more information. 762 return if !$Kernel::OM->Get('Kernel::System::Calendar')->TicketAppointmentDelete( 763 TicketID => $Param{TicketID}, 764 ); 765 766 # delete ticket_history 767 return if !$Self->HistoryDelete( 768 TicketID => $Param{TicketID}, 769 UserID => $Param{UserID}, 770 ); 771 772 # Delete all articles and associated data. 773 my @Articles = $ArticleObject->ArticleList( TicketID => $Param{TicketID} ); 774 for my $MetaArticle (@Articles) { 775 return if !$ArticleObject->BackendForArticle( %{$MetaArticle} )->ArticleDelete( 776 ArticleID => $MetaArticle->{ArticleID}, 777 %Param, 778 ); 779 } 780 781 # delete ticket 782 return if !$DBObject->Do( 783 SQL => 'DELETE FROM ticket WHERE id = ?', 784 Bind => [ \$Param{TicketID} ], 785 ); 786 787 # trigger event 788 $Self->EventHandler( 789 Event => 'TicketDelete', 790 Data => { 791 TicketID => $Param{TicketID}, 792 }, 793 UserID => $Param{UserID}, 794 ); 795 796 # Clear ticket cache again, in case it was rebuilt in the meantime. 797 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 798 799 return 1; 800} 801 802=head2 TicketIDLookup() 803 804ticket id lookup by ticket number 805 806 my $TicketID = $TicketObject->TicketIDLookup( 807 TicketNumber => '2004040510440485', 808 ); 809 810=cut 811 812sub TicketIDLookup { 813 my ( $Self, %Param ) = @_; 814 815 # check needed stuff 816 if ( !$Param{TicketNumber} ) { 817 $Kernel::OM->Get('Kernel::System::Log')->Log( 818 Priority => 'error', 819 Message => 'Need TicketNumber!' 820 ); 821 return; 822 } 823 824 # get database object 825 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 826 827 # db query 828 return if !$DBObject->Prepare( 829 SQL => 'SELECT id FROM ticket WHERE tn = ?', 830 Bind => [ \$Param{TicketNumber} ], 831 Limit => 1, 832 ); 833 834 my $ID; 835 while ( my @Row = $DBObject->FetchrowArray() ) { 836 $ID = $Row[0]; 837 } 838 839 return $ID; 840} 841 842=head2 TicketNumberLookup() 843 844ticket number lookup by ticket id 845 846 my $TicketNumber = $TicketObject->TicketNumberLookup( 847 TicketID => 123, 848 ); 849 850=cut 851 852sub TicketNumberLookup { 853 my ( $Self, %Param ) = @_; 854 855 # check needed stuff 856 if ( !$Param{TicketID} ) { 857 $Kernel::OM->Get('Kernel::System::Log')->Log( 858 Priority => 'error', 859 Message => 'Need TicketID!', 860 ); 861 return; 862 } 863 864 # get database object 865 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 866 867 # db query 868 return if !$DBObject->Prepare( 869 SQL => 'SELECT tn FROM ticket WHERE id = ?', 870 Bind => [ \$Param{TicketID} ], 871 Limit => 1, 872 ); 873 874 my $Number; 875 while ( my @Row = $DBObject->FetchrowArray() ) { 876 $Number = $Row[0]; 877 } 878 879 return $Number; 880} 881 882=head2 TicketSubjectBuild() 883 884rebuild a new ticket subject 885 886This will generate a subject like C<RE: [Ticket# 2004040510440485] Some subject> 887 888 my $NewSubject = $TicketObject->TicketSubjectBuild( 889 TicketNumber => '2004040510440485', 890 Subject => $OldSubject, 891 Action => 'Reply', 892 ); 893 894This will generate a subject like C<[Ticket# 2004040510440485] Some subject> 895(so without RE: ) 896 897 my $NewSubject = $TicketObject->TicketSubjectBuild( 898 TicketNumber => '2004040510440485', 899 Subject => $OldSubject, 900 Type => 'New', 901 Action => 'Reply', 902 ); 903 904This will generate a subject like C<FWD: [Ticket# 2004040510440485] Some subject> 905 906 my $NewSubject = $TicketObject->TicketSubjectBuild( 907 TicketNumber => '2004040510440485', 908 Subject => $OldSubject, 909 Action => 'Forward', # Possible values are Reply and Forward, Reply is default. 910 ); 911 912This will generate a subject like C<[Ticket# 2004040510440485] Re: Some subject> 913(so without clean-up of subject) 914 915 my $NewSubject = $TicketObject->TicketSubjectBuild( 916 TicketNumber => '2004040510440485', 917 Subject => $OldSubject, 918 Type => 'New', 919 NoCleanup => 1, 920 ); 921 922=cut 923 924sub TicketSubjectBuild { 925 my ( $Self, %Param ) = @_; 926 927 # check needed stuff 928 if ( !defined $Param{TicketNumber} ) { 929 $Kernel::OM->Get('Kernel::System::Log')->Log( 930 Priority => 'error', 931 Message => "Need TicketNumber!" 932 ); 933 return; 934 } 935 my $Subject = $Param{Subject} || ''; 936 my $Action = $Param{Action} || 'Reply'; 937 938 # cleanup of subject, remove existing ticket numbers and reply indentifier 939 if ( !$Param{NoCleanup} ) { 940 $Subject = $Self->TicketSubjectClean(%Param); 941 } 942 943 # get config object 944 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 945 946 # get config options 947 my $TicketHook = $ConfigObject->Get('Ticket::Hook'); 948 my $TicketHookDivider = $ConfigObject->Get('Ticket::HookDivider'); 949 my $TicketSubjectRe = $ConfigObject->Get('Ticket::SubjectRe'); 950 my $TicketSubjectFwd = $ConfigObject->Get('Ticket::SubjectFwd'); 951 my $TicketSubjectFormat = $ConfigObject->Get('Ticket::SubjectFormat') || 'Left'; 952 953 # return subject for new tickets 954 if ( $Param{Type} && $Param{Type} eq 'New' ) { 955 if ( lc $TicketSubjectFormat eq 'right' ) { 956 return $Subject . " [$TicketHook$TicketHookDivider$Param{TicketNumber}]"; 957 } 958 if ( lc $TicketSubjectFormat eq 'none' ) { 959 return $Subject; 960 } 961 return "[$TicketHook$TicketHookDivider$Param{TicketNumber}] " . $Subject; 962 } 963 964 # return subject for existing tickets 965 if ( $Action eq 'Forward' ) { 966 if ($TicketSubjectFwd) { 967 $TicketSubjectFwd .= ': '; 968 } 969 if ( lc $TicketSubjectFormat eq 'right' ) { 970 return $TicketSubjectFwd . $Subject 971 . " [$TicketHook$TicketHookDivider$Param{TicketNumber}]"; 972 } 973 if ( lc $TicketSubjectFormat eq 'none' ) { 974 return $TicketSubjectFwd . $Subject; 975 } 976 return 977 $TicketSubjectFwd 978 . "[$TicketHook$TicketHookDivider$Param{TicketNumber}] " 979 . $Subject; 980 } 981 else { 982 if ($TicketSubjectRe) { 983 $TicketSubjectRe .= ': '; 984 } 985 if ( lc $TicketSubjectFormat eq 'right' ) { 986 return $TicketSubjectRe . $Subject 987 . " [$TicketHook$TicketHookDivider$Param{TicketNumber}]"; 988 } 989 if ( lc $TicketSubjectFormat eq 'none' ) { 990 return $TicketSubjectRe . $Subject; 991 } 992 return $TicketSubjectRe . "[$TicketHook$TicketHookDivider$Param{TicketNumber}] " . $Subject; 993 } 994} 995 996=head2 TicketSubjectClean() 997 998strip/clean up a ticket subject 999 1000 my $NewSubject = $TicketObject->TicketSubjectClean( 1001 TicketNumber => '2004040510440485', 1002 Subject => $OldSubject, 1003 Size => $SubjectSizeToBeDisplayed # optional, if 0 do not cut subject 1004 ); 1005 1006=cut 1007 1008sub TicketSubjectClean { 1009 my ( $Self, %Param ) = @_; 1010 1011 # check needed stuff 1012 if ( !defined $Param{TicketNumber} ) { 1013 $Kernel::OM->Get('Kernel::System::Log')->Log( 1014 Priority => 'error', 1015 Message => "Need TicketNumber!" 1016 ); 1017 return; 1018 } 1019 1020 my $Subject = $Param{Subject} || ''; 1021 1022 # get config object 1023 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1024 1025 # get config options 1026 my $TicketHook = $ConfigObject->Get('Ticket::Hook'); 1027 my $TicketHookDivider = $ConfigObject->Get('Ticket::HookDivider'); 1028 my $TicketSubjectSize = $Param{Size}; 1029 if ( !defined $TicketSubjectSize ) { 1030 $TicketSubjectSize = $ConfigObject->Get('Ticket::SubjectSize') 1031 || 120; 1032 } 1033 my $TicketSubjectRe = $ConfigObject->Get('Ticket::SubjectRe'); 1034 my $TicketSubjectFwd = $ConfigObject->Get('Ticket::SubjectFwd'); 1035 1036 # remove all possible ticket hook formats with [] 1037 $Subject =~ s/\[\s*\Q$TicketHook: $Param{TicketNumber}\E\s*\]\s*//g; 1038 $Subject =~ s/\[\s*\Q$TicketHook:$Param{TicketNumber}\E\s*\]\s*//g; 1039 $Subject =~ s/\[\s*\Q$TicketHook$TicketHookDivider$Param{TicketNumber}\E\s*\]\s*//g; 1040 1041 # remove all ticket numbers with [] 1042 if ( $ConfigObject->Get('Ticket::SubjectCleanAllNumbers') ) { 1043 $Subject =~ s/\[\s*\Q$TicketHook$TicketHookDivider\E\d+?\s*\]\s*//g; 1044 } 1045 1046 # remove all possible ticket hook formats without [] 1047 $Subject =~ s/\Q$TicketHook: $Param{TicketNumber}\E\s*//g; 1048 $Subject =~ s/\Q$TicketHook:$Param{TicketNumber}\E\s*//g; 1049 $Subject =~ s/\Q$TicketHook$TicketHookDivider$Param{TicketNumber}\E\s*//g; 1050 1051 # remove all ticket numbers without [] 1052 if ( $ConfigObject->Get('Ticket::SubjectCleanAllNumbers') ) { 1053 $Subject =~ s/\Q$TicketHook$TicketHookDivider\E\d+?\s*//g; 1054 } 1055 1056 # remove leading number with configured "RE:\s" or "RE[\d+]:\s" e. g. "RE: " or "RE[4]: " 1057 $Subject =~ s/^($TicketSubjectRe(\[\d+\])?:\s)+//i; 1058 1059 # remove leading number with configured "Fwd:\s" or "Fwd[\d+]:\s" e. g. "Fwd: " or "Fwd[4]: " 1060 $Subject =~ s/^($TicketSubjectFwd(\[\d+\])?:\s)+//i; 1061 1062 # trim white space at the beginning or end 1063 $Subject =~ s/(^\s+|\s+$)//; 1064 1065 # resize subject based on config 1066 # do not cut subject, if size parameter was 0 1067 if ($TicketSubjectSize) { 1068 $Subject =~ s/^(.{$TicketSubjectSize}).*$/$1 [...]/; 1069 } 1070 1071 return $Subject; 1072} 1073 1074=head2 TicketGet() 1075 1076Get ticket info 1077 1078 my %Ticket = $TicketObject->TicketGet( 1079 TicketID => 123, 1080 DynamicFields => 0, # Optional, default 0. To include the dynamic field values for this ticket on the return structure. 1081 UserID => 123, 1082 Silent => 0, # Optional, default 0. To suppress the warning if the ticket does not exist. 1083 ); 1084 1085Returns: 1086 1087 %Ticket = ( 1088 TicketNumber => '20101027000001', 1089 Title => 'some title', 1090 TicketID => 123, 1091 State => 'some state', 1092 StateID => 123, 1093 StateType => 'some state type', 1094 Priority => 'some priority', 1095 PriorityID => 123, 1096 Lock => 'lock', 1097 LockID => 123, 1098 Queue => 'some queue', 1099 QueueID => 123, 1100 CustomerID => 'customer_id_123', 1101 CustomerUserID => 'customer_user_id_123', 1102 Owner => 'some_owner_login', 1103 OwnerID => 123, 1104 Type => 'some ticket type', 1105 TypeID => 123, 1106 SLA => 'some sla', 1107 SLAID => 123, 1108 Service => 'some service', 1109 ServiceID => 123, 1110 Responsible => 'some_responsible_login', 1111 ResponsibleID => 123, 1112 Age => 3456, 1113 Created => '2010-10-27 20:15:00' 1114 CreateBy => 123, 1115 Changed => '2010-10-27 20:15:15', 1116 ChangeBy => 123, 1117 ArchiveFlag => 'y', 1118 1119 # If DynamicFields => 1 was passed, you'll get an entry like this for each dynamic field: 1120 DynamicField_X => 'value_x', 1121 1122 # (time stamps of expected escalations) 1123 EscalationResponseTime (unix time stamp of response time escalation) 1124 EscalationUpdateTime (unix time stamp of update time escalation) 1125 EscalationSolutionTime (unix time stamp of solution time escalation) 1126 1127 # (general escalation info of nearest escalation type) 1128 EscalationDestinationIn (escalation in e. g. 1h 4m) 1129 EscalationDestinationTime (date of escalation in unix time, e. g. 72193292) 1130 EscalationDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00") 1131 EscalationTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800") 1132 EscalationTime (seconds total till escalation of nearest escalation time type - response, update or solution time, e. g. "3600") 1133 1134 # (detailed escalation info about first response, update and solution time) 1135 FirstResponseTimeEscalation (if true, ticket is escalated) 1136 FirstResponseTimeNotification (if true, notify - x% of escalation has reached) 1137 FirstResponseTimeDestinationTime (date of escalation in unix time, e. g. 72193292) 1138 FirstResponseTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00") 1139 FirstResponseTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800") 1140 FirstResponseTime (seconds total till escalation, e. g. "3600") 1141 1142 UpdateTimeEscalation (if true, ticket is escalated) 1143 UpdateTimeNotification (if true, notify - x% of escalation has reached) 1144 UpdateTimeDestinationTime (date of escalation in unix time, e. g. 72193292) 1145 UpdateTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00") 1146 UpdateTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800") 1147 UpdateTime (seconds total till escalation, e. g. "3600") 1148 1149 SolutionTimeEscalation (if true, ticket is escalated) 1150 SolutionTimeNotification (if true, notify - x% of escalation has reached) 1151 SolutionTimeDestinationTime (date of escalation in unix time, e. g. 72193292) 1152 SolutionTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00") 1153 SolutionTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800") 1154 SolutionTime (seconds total till escalation, e. g. "3600") 1155 ); 1156 1157To get extended ticket attributes, use C<Extended> 1158 1159 my %Ticket = $TicketObject->TicketGet( 1160 TicketID => 123, 1161 UserID => 123, 1162 Extended => 1, 1163 ); 1164 1165Additional parameters are: 1166 1167 %Ticket = ( 1168 FirstResponse (timestamp of first response, first contact with customer) 1169 FirstResponseInMin (minutes till first response) 1170 FirstResponseDiffInMin (minutes till or over first response) 1171 1172 SolutionInMin (minutes till solution time) 1173 SolutionDiffInMin (minutes till or over solution time) 1174 1175 FirstLock (timestamp of first lock) 1176 ); 1177 1178=cut 1179 1180sub TicketGet { 1181 my ( $Self, %Param ) = @_; 1182 1183 # check needed stuff 1184 if ( !$Param{TicketID} ) { 1185 $Kernel::OM->Get('Kernel::System::Log')->Log( 1186 Priority => 'error', 1187 Message => 'Need TicketID!' 1188 ); 1189 return; 1190 } 1191 $Param{Extended} = $Param{Extended} ? 1 : 0; 1192 1193 # Caching TicketGet() is a bit more complex than usual. 1194 # The full function result will be cached in an in-memory cache to 1195 # speed up subsequent operations in one request, but not on disk, 1196 # because there are dependencies to other objects such as queue which cannot 1197 # easily be tracked. 1198 # The SQL for fetching ticket data will be cached on disk as well because this cache 1199 # can easily be invalidated on ticket changes. 1200 1201 # check cache 1202 my $FetchDynamicFields = $Param{DynamicFields} ? 1 : 0; 1203 1204 my $CacheKey = 'Cache::GetTicket' . $Param{TicketID}; 1205 my $CacheKeyDynamicFields 1206 = 'Cache::GetTicket' . $Param{TicketID} . '::' . $Param{Extended} . '::' . $FetchDynamicFields; 1207 1208 my $CachedDynamicFields = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1209 Type => $Self->{CacheType}, 1210 Key => $CacheKeyDynamicFields, 1211 CacheInMemory => 1, 1212 CacheInBackend => 0, 1213 ); 1214 1215 # check if result is cached 1216 if ( ref $CachedDynamicFields eq 'HASH' ) { 1217 return %{$CachedDynamicFields}; 1218 } 1219 1220 my %Ticket; 1221 1222 my $Cached = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1223 Type => $Self->{CacheType}, 1224 Key => $CacheKey, 1225 ); 1226 1227 if ( ref $Cached eq 'HASH' ) { 1228 %Ticket = %{$Cached}; 1229 } 1230 else { 1231 1232 # get database object 1233 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1234 1235 return if !$DBObject->Prepare( 1236 SQL => ' 1237 SELECT st.id, st.queue_id, st.ticket_state_id, st.ticket_lock_id, st.ticket_priority_id, 1238 st.create_time, st.create_time, st.tn, st.customer_id, st.customer_user_id, 1239 st.user_id, st.responsible_user_id, st.until_time, st.change_time, st.title, 1240 st.escalation_update_time, st.timeout, st.type_id, st.service_id, st.sla_id, 1241 st.escalation_response_time, st.escalation_solution_time, st.escalation_time, st.archive_flag, 1242 st.create_by, st.change_by 1243 FROM ticket st 1244 WHERE st.id = ?', 1245 Bind => [ \$Param{TicketID} ], 1246 Limit => 1, 1247 ); 1248 1249 while ( my @Row = $DBObject->FetchrowArray() ) { 1250 $Ticket{TicketID} = $Row[0]; 1251 $Ticket{QueueID} = $Row[1]; 1252 $Ticket{StateID} = $Row[2]; 1253 $Ticket{LockID} = $Row[3]; 1254 $Ticket{PriorityID} = $Row[4]; 1255 1256 $Ticket{Created} = $Row[5]; 1257 $Ticket{TicketNumber} = $Row[7]; 1258 $Ticket{CustomerID} = $Row[8]; 1259 $Ticket{CustomerUserID} = $Row[9]; 1260 1261 $Ticket{OwnerID} = $Row[10]; 1262 $Ticket{ResponsibleID} = $Row[11] || 1; 1263 $Ticket{RealTillTimeNotUsed} = $Row[12]; 1264 $Ticket{Changed} = $Row[13]; 1265 $Ticket{Title} = $Row[14]; 1266 1267 $Ticket{EscalationUpdateTime} = $Row[15]; 1268 $Ticket{UnlockTimeout} = $Row[16]; 1269 $Ticket{TypeID} = $Row[17] || 1; 1270 $Ticket{ServiceID} = $Row[18] || ''; 1271 $Ticket{SLAID} = $Row[19] || ''; 1272 1273 $Ticket{EscalationResponseTime} = $Row[20]; 1274 $Ticket{EscalationSolutionTime} = $Row[21]; 1275 $Ticket{EscalationTime} = $Row[22]; 1276 $Ticket{ArchiveFlag} = $Row[23] ? 'y' : 'n'; 1277 1278 $Ticket{CreateBy} = $Row[24]; 1279 $Ticket{ChangeBy} = $Row[25]; 1280 } 1281 1282 # use cache only when a ticket number is found otherwise a non-existant ticket 1283 # is cached. That can cause errors when the cache isn't expired and postmaster 1284 # creates that ticket 1285 if ( $Ticket{TicketID} ) { 1286 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1287 Type => $Self->{CacheType}, 1288 TTL => $Self->{CacheTTL}, 1289 Key => $CacheKey, 1290 1291 # make a local copy of the ticket data to avoid it being altered in-memory later 1292 Value => {%Ticket}, 1293 ); 1294 } 1295 } 1296 1297 # check ticket 1298 if ( !$Ticket{TicketID} ) { 1299 if ( !$Param{Silent} ) { 1300 $Kernel::OM->Get('Kernel::System::Log')->Log( 1301 Priority => 'error', 1302 Message => "No such TicketID ($Param{TicketID})!", 1303 ); 1304 } 1305 return; 1306 } 1307 1308 # check if need to return DynamicFields 1309 if ($FetchDynamicFields) { 1310 1311 # get dynamic field objects 1312 my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); 1313 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 1314 1315 # get all dynamic fields for the object type Ticket 1316 my $DynamicFieldList = $DynamicFieldObject->DynamicFieldListGet( 1317 ObjectType => 'Ticket' 1318 ); 1319 1320 DYNAMICFIELD: 1321 for my $DynamicFieldConfig ( @{$DynamicFieldList} ) { 1322 1323 # validate each dynamic field 1324 next DYNAMICFIELD if !$DynamicFieldConfig; 1325 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1326 next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; 1327 1328 # get the current value for each dynamic field 1329 my $Value = $DynamicFieldBackendObject->ValueGet( 1330 DynamicFieldConfig => $DynamicFieldConfig, 1331 ObjectID => $Ticket{TicketID}, 1332 ); 1333 1334 # set the dynamic field name and value into the ticket hash 1335 $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $Value; 1336 } 1337 } 1338 1339 my %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( 1340 ID => $Ticket{QueueID}, 1341 ); 1342 1343 $Ticket{Queue} = $Queue{Name}; 1344 $Ticket{GroupID} = $Queue{GroupID}; 1345 1346 # fillup runtime values 1347 my $TicketCreatedDTObj = $Kernel::OM->Create( 1348 'Kernel::System::DateTime', 1349 ObjectParams => { 1350 String => $Ticket{Created} 1351 }, 1352 ); 1353 1354 my $Delta = $TicketCreatedDTObj->Delta( DateTimeObject => $Kernel::OM->Create('Kernel::System::DateTime') ); 1355 $Ticket{Age} = $Delta->{AbsoluteSeconds}; 1356 1357 $Ticket{Priority} = $Kernel::OM->Get('Kernel::System::Priority')->PriorityLookup( 1358 PriorityID => $Ticket{PriorityID}, 1359 ); 1360 1361 # get user object 1362 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 1363 1364 # get owner 1365 $Ticket{Owner} = $UserObject->UserLookup( 1366 UserID => $Ticket{OwnerID}, 1367 ); 1368 1369 # get responsible 1370 $Ticket{Responsible} = $UserObject->UserLookup( 1371 UserID => $Ticket{ResponsibleID}, 1372 ); 1373 1374 # get lock 1375 $Ticket{Lock} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup( 1376 LockID => $Ticket{LockID}, 1377 ); 1378 1379 # get type 1380 $Ticket{Type} = $Kernel::OM->Get('Kernel::System::Type')->TypeLookup( TypeID => $Ticket{TypeID} ); 1381 1382 # get service 1383 if ( $Ticket{ServiceID} ) { 1384 1385 $Ticket{Service} = $Kernel::OM->Get('Kernel::System::Service')->ServiceLookup( 1386 ServiceID => $Ticket{ServiceID}, 1387 ); 1388 } 1389 1390 # get sla 1391 if ( $Ticket{SLAID} ) { 1392 $Ticket{SLA} = $Kernel::OM->Get('Kernel::System::SLA')->SLALookup( 1393 SLAID => $Ticket{SLAID}, 1394 ); 1395 } 1396 1397 # get state info 1398 my %StateData = $Kernel::OM->Get('Kernel::System::State')->StateGet( 1399 ID => $Ticket{StateID} 1400 ); 1401 1402 $Ticket{StateType} = $StateData{TypeName}; 1403 $Ticket{State} = $StateData{Name}; 1404 1405 if ( !$Ticket{RealTillTimeNotUsed} || lc $StateData{TypeName} eq 'pending' ) { 1406 $Ticket{UntilTime} = 0; 1407 } 1408 else { 1409 $Ticket{UntilTime} = $Ticket{RealTillTimeNotUsed} - $Kernel::OM->Create('Kernel::System::DateTime')->ToEpoch(); 1410 } 1411 1412 # get escalation attributes 1413 my %Escalation = $Self->TicketEscalationDateCalculation( 1414 Ticket => \%Ticket, 1415 UserID => $Param{UserID} || 1, 1416 ); 1417 1418 for my $Key ( sort keys %Escalation ) { 1419 $Ticket{$Key} = $Escalation{$Key}; 1420 } 1421 1422 # do extended lookups 1423 if ( $Param{Extended} ) { 1424 my %TicketExtended = $Self->_TicketGetExtended( 1425 TicketID => $Param{TicketID}, 1426 Ticket => \%Ticket, 1427 ); 1428 for my $Key ( sort keys %TicketExtended ) { 1429 $Ticket{$Key} = $TicketExtended{$Key}; 1430 } 1431 } 1432 1433 # cache user result 1434 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1435 Type => $Self->{CacheType}, 1436 TTL => $Self->{CacheTTL}, 1437 Key => $CacheKeyDynamicFields, 1438 1439 # make a local copy of the ticket data to avoid it being altered in-memory later 1440 Value => {%Ticket}, 1441 CacheInMemory => 1, 1442 CacheInBackend => 0, 1443 ); 1444 1445 return %Ticket; 1446} 1447 1448=head2 TicketTitleUpdate() 1449 1450update ticket title 1451 1452 my $Success = $TicketObject->TicketTitleUpdate( 1453 Title => 'Some Title', 1454 TicketID => 123, 1455 UserID => 1, 1456 ); 1457 1458Events: 1459 TicketTitleUpdate 1460 1461=cut 1462 1463sub TicketTitleUpdate { 1464 my ( $Self, %Param ) = @_; 1465 1466 # check needed stuff 1467 for my $Needed (qw(Title TicketID UserID)) { 1468 if ( !defined $Param{$Needed} ) { 1469 $Kernel::OM->Get('Kernel::System::Log')->Log( 1470 Priority => 'error', 1471 Message => "Need $Needed!" 1472 ); 1473 return; 1474 } 1475 } 1476 1477 # check if update is needed 1478 my %Ticket = $Self->TicketGet( 1479 TicketID => $Param{TicketID}, 1480 UserID => $Param{UserID}, 1481 DynamicFields => 0, 1482 ); 1483 1484 return 1 if defined $Ticket{Title} && $Ticket{Title} eq $Param{Title}; 1485 1486 # db access 1487 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 1488 SQL => 'UPDATE ticket SET title = ?, change_time = current_timestamp, ' 1489 . ' change_by = ? WHERE id = ?', 1490 Bind => [ \$Param{Title}, \$Param{UserID}, \$Param{TicketID} ], 1491 ); 1492 1493 # clear ticket cache 1494 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 1495 1496 # truncate title 1497 my $Title = substr( $Param{Title}, 0, 50 ); 1498 $Title .= '...' if length($Title) == 50; 1499 1500 # history insert 1501 $Self->HistoryAdd( 1502 TicketID => $Param{TicketID}, 1503 HistoryType => 'TitleUpdate', 1504 Name => "\%\%$Ticket{Title}\%\%$Title", 1505 CreateUserID => $Param{UserID}, 1506 ); 1507 1508 # trigger event 1509 $Self->EventHandler( 1510 Event => 'TicketTitleUpdate', 1511 Data => { 1512 TicketID => $Param{TicketID}, 1513 }, 1514 UserID => $Param{UserID}, 1515 ); 1516 1517 return 1; 1518} 1519 1520=head2 TicketUnlockTimeoutUpdate() 1521 1522set the ticket unlock time to the passed time 1523 1524 my $Success = $TicketObject->TicketUnlockTimeoutUpdate( 1525 UnlockTimeout => $Epoch, 1526 TicketID => 123, 1527 UserID => 143, 1528 ); 1529 1530Events: 1531 TicketUnlockTimeoutUpdate 1532 1533=cut 1534 1535sub TicketUnlockTimeoutUpdate { 1536 my ( $Self, %Param ) = @_; 1537 1538 # check needed stuff 1539 for my $Needed (qw(UnlockTimeout TicketID UserID)) { 1540 if ( !defined $Param{$Needed} ) { 1541 $Kernel::OM->Get('Kernel::System::Log')->Log( 1542 Priority => 'error', 1543 Message => "Need $Needed!" 1544 ); 1545 return; 1546 } 1547 } 1548 1549 # check if update is needed 1550 my %Ticket = $Self->TicketGet( 1551 %Param, 1552 DynamicFields => 0, 1553 ); 1554 1555 return 1 if $Ticket{UnlockTimeout} eq $Param{UnlockTimeout}; 1556 1557 # reset unlock time 1558 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 1559 SQL => 'UPDATE ticket SET timeout = ?, change_time = current_timestamp, ' 1560 . ' change_by = ? WHERE id = ?', 1561 Bind => [ \$Param{UnlockTimeout}, \$Param{UserID}, \$Param{TicketID} ], 1562 ); 1563 1564 # clear ticket cache 1565 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 1566 1567 # add history 1568 $Self->HistoryAdd( 1569 TicketID => $Param{TicketID}, 1570 CreateUserID => $Param{UserID}, 1571 HistoryType => 'Misc', 1572 Name => Translatable('Reset of unlock time.'), 1573 ); 1574 1575 # trigger event 1576 $Self->EventHandler( 1577 Event => 'TicketUnlockTimeoutUpdate', 1578 Data => { 1579 TicketID => $Param{TicketID}, 1580 }, 1581 UserID => $Param{UserID}, 1582 ); 1583 1584 return 1; 1585} 1586 1587=head2 TicketQueueID() 1588 1589get ticket queue id 1590 1591 my $QueueID = $TicketObject->TicketQueueID( 1592 TicketID => 123, 1593 ); 1594 1595=cut 1596 1597sub TicketQueueID { 1598 my ( $Self, %Param ) = @_; 1599 1600 # check needed stuff 1601 if ( !$Param{TicketID} ) { 1602 $Kernel::OM->Get('Kernel::System::Log')->Log( 1603 Priority => 'error', 1604 Message => 'Need TicketID!' 1605 ); 1606 return; 1607 } 1608 1609 # get ticket data 1610 my %Ticket = $Self->TicketGet( 1611 TicketID => $Param{TicketID}, 1612 DynamicFields => 0, 1613 UserID => 1, 1614 Silent => 1, 1615 ); 1616 1617 return if !%Ticket; 1618 1619 return $Ticket{QueueID}; 1620} 1621 1622=head2 TicketMoveList() 1623 1624to get the move queue list for a ticket (depends on workflow, if configured) 1625 1626 my %Queues = $TicketObject->TicketMoveList( 1627 Type => 'create', 1628 UserID => 123, 1629 ); 1630 1631 my %Queues = $TicketObject->TicketMoveList( 1632 Type => 'create', 1633 CustomerUserID => 'customer_user_id_123', 1634 ); 1635 1636 1637 my %Queues = $TicketObject->TicketMoveList( 1638 QueueID => 123, 1639 UserID => 123, 1640 ); 1641 1642 my %Queues = $TicketObject->TicketMoveList( 1643 TicketID => 123, 1644 UserID => 123, 1645 ); 1646 1647=cut 1648 1649sub TicketMoveList { 1650 my ( $Self, %Param ) = @_; 1651 1652 # check needed stuff 1653 if ( !$Param{UserID} && !$Param{CustomerUserID} ) { 1654 $Kernel::OM->Get('Kernel::System::Log')->Log( 1655 Priority => 'error', 1656 Message => 'Need UserID or CustomerUserID!', 1657 ); 1658 return; 1659 } 1660 1661 # check needed stuff 1662 if ( !$Param{QueueID} && !$Param{TicketID} && !$Param{Type} ) { 1663 $Kernel::OM->Get('Kernel::System::Log')->Log( 1664 Priority => 'error', 1665 Message => 'Need QueueID, TicketID or Type!', 1666 ); 1667 return; 1668 } 1669 1670 # get queue object 1671 my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); 1672 1673 my %Queues; 1674 if ( $Param{UserID} && $Param{UserID} eq 1 ) { 1675 %Queues = $QueueObject->GetAllQueues(); 1676 } 1677 else { 1678 %Queues = $QueueObject->GetAllQueues(%Param); 1679 } 1680 1681 # workflow 1682 my $ACL = $Self->TicketAcl( 1683 %Param, 1684 ReturnType => 'Ticket', 1685 ReturnSubType => 'Queue', 1686 Data => \%Queues, 1687 ); 1688 return $Self->TicketAclData() if $ACL; 1689 return %Queues; 1690} 1691 1692=head2 TicketQueueSet() 1693 1694to move a ticket (sends notification to agents of selected my queues, if ticket is not closed) 1695 1696 my $Success = $TicketObject->TicketQueueSet( 1697 QueueID => 123, 1698 TicketID => 123, 1699 UserID => 123, 1700 ); 1701 1702 my $Success = $TicketObject->TicketQueueSet( 1703 Queue => 'Some Queue Name', 1704 TicketID => 123, 1705 UserID => 123, 1706 ); 1707 1708 my $Success = $TicketObject->TicketQueueSet( 1709 Queue => 'Some Queue Name', 1710 TicketID => 123, 1711 Comment => 'some comment', # optional 1712 ForceNotificationToUserID => [1,43,56], # if you want to force somebody 1713 UserID => 123, 1714 ); 1715 1716Optional attribute: 1717SendNoNotification disables or enables agent and customer notification for this 1718action. 1719 1720For example: 1721 1722 SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) 1723 1724Events: 1725 TicketQueueUpdate 1726 1727=cut 1728 1729sub TicketQueueSet { 1730 my ( $Self, %Param ) = @_; 1731 1732 # get queue object 1733 my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); 1734 1735 # queue lookup 1736 if ( $Param{Queue} && !$Param{QueueID} ) { 1737 $Param{QueueID} = $QueueObject->QueueLookup( Queue => $Param{Queue} ); 1738 } 1739 1740 # check needed stuff 1741 for my $Needed (qw(TicketID QueueID UserID)) { 1742 if ( !$Param{$Needed} ) { 1743 $Kernel::OM->Get('Kernel::System::Log')->Log( 1744 Priority => 'error', 1745 Message => "Need $Needed!" 1746 ); 1747 return; 1748 } 1749 } 1750 1751 # get current ticket 1752 my %Ticket = $Self->TicketGet( 1753 %Param, 1754 DynamicFields => 0, 1755 ); 1756 1757 # move needed? 1758 if ( $Param{QueueID} == $Ticket{QueueID} && !$Param{Comment} ) { 1759 1760 # update not needed 1761 return 1; 1762 } 1763 1764 # permission check 1765 my %MoveList = $Self->MoveList( %Param, Type => 'move_into' ); 1766 if ( !$MoveList{ $Param{QueueID} } ) { 1767 $Kernel::OM->Get('Kernel::System::Log')->Log( 1768 Priority => 'notice', 1769 Message => "Permission denied on TicketID: $Param{TicketID}!", 1770 ); 1771 return; 1772 } 1773 1774 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 1775 SQL => 'UPDATE ticket SET queue_id = ?, change_time = current_timestamp, ' 1776 . ' change_by = ? WHERE id = ?', 1777 Bind => [ \$Param{QueueID}, \$Param{UserID}, \$Param{TicketID} ], 1778 ); 1779 1780 # queue lookup 1781 my $Queue = $QueueObject->QueueLookup( QueueID => $Param{QueueID} ); 1782 1783 # clear ticket cache 1784 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 1785 1786 # history insert 1787 $Self->HistoryAdd( 1788 TicketID => $Param{TicketID}, 1789 QueueID => $Param{QueueID}, 1790 HistoryType => 'Move', 1791 Name => "\%\%$Queue\%\%$Param{QueueID}\%\%$Ticket{Queue}\%\%$Ticket{QueueID}", 1792 CreateUserID => $Param{UserID}, 1793 ); 1794 1795 # send move notify to queue subscriber 1796 if ( !$Param{SendNoNotification} && $Ticket{StateType} ne 'closed' ) { 1797 1798 my @UserIDs; 1799 1800 if ( $Param{ForceNotificationToUserID} ) { 1801 push @UserIDs, @{ $Param{ForceNotificationToUserID} }; 1802 } 1803 1804 # trigger notification event 1805 $Self->EventHandler( 1806 Event => 'NotificationMove', 1807 Data => { 1808 TicketID => $Param{TicketID}, 1809 CustomerMessageParams => { 1810 Queue => $Queue, 1811 }, 1812 Recipients => \@UserIDs, 1813 }, 1814 UserID => $Param{UserID}, 1815 ); 1816 } 1817 1818 # trigger event, OldTicketData is needed for escalation events 1819 $Self->EventHandler( 1820 Event => 'TicketQueueUpdate', 1821 Data => { 1822 TicketID => $Param{TicketID}, 1823 OldTicketData => \%Ticket, 1824 }, 1825 UserID => $Param{UserID}, 1826 ); 1827 1828 return 1; 1829} 1830 1831=head2 TicketMoveQueueList() 1832 1833returns a list of used queue ids / names 1834 1835 my @QueueIDList = $TicketObject->TicketMoveQueueList( 1836 TicketID => 123, 1837 Type => 'ID', 1838 ); 1839 1840Returns: 1841 1842 @QueueIDList = ( 1, 2, 3 ); 1843 1844 my @QueueList = $TicketObject->TicketMoveQueueList( 1845 TicketID => 123, 1846 Type => 'Name', 1847 ); 1848 1849Returns: 1850 1851 @QueueList = ( 'QueueA', 'QueueB', 'QueueC' ); 1852 1853=cut 1854 1855sub TicketMoveQueueList { 1856 my ( $Self, %Param ) = @_; 1857 1858 # check needed stuff 1859 if ( !$Param{TicketID} ) { 1860 $Kernel::OM->Get('Kernel::System::Log')->Log( 1861 Priority => 'error', 1862 Message => "Need TicketID!" 1863 ); 1864 return; 1865 } 1866 1867 # get database object 1868 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1869 1870 # db query 1871 return if !$DBObject->Prepare( 1872 SQL => 'SELECT sh.name, ht.name, sh.create_by, sh.queue_id FROM ' 1873 . 'ticket_history sh, ticket_history_type ht WHERE ' 1874 . 'sh.ticket_id = ? AND ht.name IN (\'Move\', \'NewTicket\') AND ' 1875 . 'ht.id = sh.history_type_id ORDER BY sh.id', 1876 Bind => [ \$Param{TicketID} ], 1877 ); 1878 1879 my @QueueID; 1880 while ( my @Row = $DBObject->FetchrowArray() ) { 1881 1882 # store result 1883 if ( $Row[1] eq 'NewTicket' ) { 1884 if ( $Row[3] ) { 1885 push @QueueID, $Row[3]; 1886 } 1887 } 1888 elsif ( $Row[1] eq 'Move' ) { 1889 if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ ) { 1890 push @QueueID, $2; 1891 } 1892 elsif ( $Row[0] =~ /^Ticket moved to Queue '.+?' \(ID=(.+?)\)/ ) { 1893 push @QueueID, $1; 1894 } 1895 } 1896 } 1897 1898 # get queue object 1899 my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); 1900 1901 # queue lookup 1902 my @QueueName; 1903 for my $QueueID (@QueueID) { 1904 1905 my $Queue = $QueueObject->QueueLookup( QueueID => $QueueID ); 1906 1907 push @QueueName, $Queue; 1908 } 1909 1910 if ( $Param{Type} && $Param{Type} eq 'Name' ) { 1911 return @QueueName; 1912 } 1913 else { 1914 return @QueueID; 1915 } 1916} 1917 1918=head2 TicketTypeList() 1919 1920to get all possible types for a ticket (depends on workflow, if configured) 1921 1922 my %Types = $TicketObject->TicketTypeList( 1923 UserID => 123, 1924 ); 1925 1926 my %Types = $TicketObject->TicketTypeList( 1927 CustomerUserID => 'customer_user_id_123', 1928 ); 1929 1930 my %Types = $TicketObject->TicketTypeList( 1931 QueueID => 123, 1932 UserID => 123, 1933 ); 1934 1935 my %Types = $TicketObject->TicketTypeList( 1936 TicketID => 123, 1937 UserID => 123, 1938 ); 1939 1940Returns: 1941 1942 %Types = ( 1943 1 => 'default', 1944 2 => 'request', 1945 3 => 'offer', 1946 ); 1947 1948=cut 1949 1950sub TicketTypeList { 1951 my ( $Self, %Param ) = @_; 1952 1953 # check needed stuff 1954 if ( !$Param{UserID} && !$Param{CustomerUserID} ) { 1955 $Kernel::OM->Get('Kernel::System::Log')->Log( 1956 Priority => 'error', 1957 Message => 'Need UserID or CustomerUserID!' 1958 ); 1959 return; 1960 } 1961 1962 my %Types = $Kernel::OM->Get('Kernel::System::Type')->TypeList( Valid => 1 ); 1963 1964 # workflow 1965 my $ACL = $Self->TicketAcl( 1966 %Param, 1967 ReturnType => 'Ticket', 1968 ReturnSubType => 'Type', 1969 Data => \%Types, 1970 ); 1971 1972 return $Self->TicketAclData() if $ACL; 1973 return %Types; 1974} 1975 1976=head2 TicketTypeSet() 1977 1978to set a ticket type 1979 1980 my $Success = $TicketObject->TicketTypeSet( 1981 TypeID => 123, 1982 TicketID => 123, 1983 UserID => 123, 1984 ); 1985 1986 my $Success = $TicketObject->TicketTypeSet( 1987 Type => 'normal', 1988 TicketID => 123, 1989 UserID => 123, 1990 ); 1991 1992Events: 1993 TicketTypeUpdate 1994 1995=cut 1996 1997sub TicketTypeSet { 1998 my ( $Self, %Param ) = @_; 1999 2000 # type lookup 2001 if ( $Param{Type} && !$Param{TypeID} ) { 2002 $Param{TypeID} = $Kernel::OM->Get('Kernel::System::Type')->TypeLookup( Type => $Param{Type} ); 2003 } 2004 2005 # check needed stuff 2006 for my $Needed (qw(TicketID TypeID UserID)) { 2007 if ( !$Param{$Needed} ) { 2008 $Kernel::OM->Get('Kernel::System::Log')->Log( 2009 Priority => 'error', 2010 Message => "Need $Needed!" 2011 ); 2012 return; 2013 } 2014 } 2015 2016 # get current ticket 2017 my %Ticket = $Self->TicketGet( 2018 %Param, 2019 DynamicFields => 0, 2020 ); 2021 2022 # update needed? 2023 return 1 if $Param{TypeID} == $Ticket{TypeID}; 2024 2025 # permission check 2026 my %TypeList = $Self->TicketTypeList(%Param); 2027 if ( !$TypeList{ $Param{TypeID} } ) { 2028 $Kernel::OM->Get('Kernel::System::Log')->Log( 2029 Priority => 'notice', 2030 Message => "Permission denied on TicketID: $Param{TicketID}!", 2031 ); 2032 return; 2033 } 2034 2035 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 2036 SQL => 'UPDATE ticket SET type_id = ?, change_time = current_timestamp, ' 2037 . ' change_by = ? WHERE id = ?', 2038 Bind => [ \$Param{TypeID}, \$Param{UserID}, \$Param{TicketID} ], 2039 ); 2040 2041 # clear ticket cache 2042 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 2043 2044 # get new ticket data 2045 my %TicketNew = $Self->TicketGet( 2046 %Param, 2047 DynamicFields => 0, 2048 ); 2049 $TicketNew{Type} = $TicketNew{Type} || 'NULL'; 2050 $Param{TypeID} = $Param{TypeID} || ''; 2051 $Ticket{Type} = $Ticket{Type} || 'NULL'; 2052 $Ticket{TypeID} = $Ticket{TypeID} || ''; 2053 2054 # history insert 2055 $Self->HistoryAdd( 2056 TicketID => $Param{TicketID}, 2057 HistoryType => 'TypeUpdate', 2058 Name => "\%\%$TicketNew{Type}\%\%$Param{TypeID}\%\%$Ticket{Type}\%\%$Ticket{TypeID}", 2059 CreateUserID => $Param{UserID}, 2060 ); 2061 2062 # trigger event 2063 $Self->EventHandler( 2064 Event => 'TicketTypeUpdate', 2065 Data => { 2066 TicketID => $Param{TicketID}, 2067 }, 2068 UserID => $Param{UserID}, 2069 ); 2070 2071 return 1; 2072} 2073 2074=head2 TicketServiceList() 2075 2076to get all possible services for a ticket (depends on workflow, if configured) 2077 2078 my %Services = $TicketObject->TicketServiceList( 2079 QueueID => 123, 2080 UserID => 123, 2081 ); 2082 2083 my %Services = $TicketObject->TicketServiceList( 2084 CustomerUserID => 123, 2085 QueueID => 123, 2086 ); 2087 2088 my %Services = $TicketObject->TicketServiceList( 2089 CustomerUserID => 123, 2090 TicketID => 123, 2091 UserID => 123, 2092 ); 2093 2094Returns: 2095 2096 %Services = ( 2097 1 => 'ServiceA', 2098 2 => 'ServiceB', 2099 3 => 'ServiceC', 2100 ); 2101 2102=cut 2103 2104sub TicketServiceList { 2105 my ( $Self, %Param ) = @_; 2106 2107 # check needed stuff 2108 if ( !$Param{UserID} && !$Param{CustomerUserID} ) { 2109 $Kernel::OM->Get('Kernel::System::Log')->Log( 2110 Priority => 'error', 2111 Message => 'UserID or CustomerUserID is needed!', 2112 ); 2113 return; 2114 } 2115 2116 # check needed stuff 2117 if ( !$Param{QueueID} && !$Param{TicketID} ) { 2118 $Kernel::OM->Get('Kernel::System::Log')->Log( 2119 Priority => 'error', 2120 Message => 'QueueID or TicketID is needed!', 2121 ); 2122 return; 2123 } 2124 2125 my $ServiceObject = $Kernel::OM->Get('Kernel::System::Service'); 2126 2127 # Return all Services, filtering by KeepChildren config. 2128 my %AllServices = $ServiceObject->ServiceList( 2129 UserID => 1, 2130 KeepChildren => 2131 $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service::KeepChildren'), 2132 ); 2133 2134 my %Services; 2135 if ( $Param{CustomerUserID} ) { 2136 2137 # Return all Services in relation with CustomerUser. 2138 my %CustomerServices = $ServiceObject->CustomerUserServiceMemberList( 2139 Result => 'HASH', 2140 CustomerUserLogin => $Param{CustomerUserID}, 2141 UserID => 1, 2142 ); 2143 2144 # Filter Services based on relation with CustomerUser and KeepChildren config. 2145 %Services = map { $_ => $CustomerServices{$_} } grep { defined $CustomerServices{$_} } sort keys %AllServices; 2146 } 2147 else { 2148 %Services = %AllServices; 2149 } 2150 2151 # workflow 2152 my $ACL = $Self->TicketAcl( 2153 %Param, 2154 ReturnType => 'Ticket', 2155 ReturnSubType => 'Service', 2156 Data => \%Services, 2157 ); 2158 2159 return $Self->TicketAclData() if $ACL; 2160 return %Services; 2161} 2162 2163=head2 TicketServiceSet() 2164 2165to set a ticket service 2166 2167 my $Success = $TicketObject->TicketServiceSet( 2168 ServiceID => 123, 2169 TicketID => 123, 2170 UserID => 123, 2171 ); 2172 2173 my $Success = $TicketObject->TicketServiceSet( 2174 Service => 'Service A', 2175 TicketID => 123, 2176 UserID => 123, 2177 ); 2178 2179Events: 2180 TicketServiceUpdate 2181 2182=cut 2183 2184sub TicketServiceSet { 2185 my ( $Self, %Param ) = @_; 2186 2187 # service lookup 2188 if ( $Param{Service} && !$Param{ServiceID} ) { 2189 $Param{ServiceID} = $Kernel::OM->Get('Kernel::System::Service')->ServiceLookup( 2190 Name => $Param{Service}, 2191 ); 2192 } 2193 2194 # check needed stuff 2195 for my $Needed (qw(TicketID ServiceID UserID)) { 2196 if ( !defined $Param{$Needed} ) { 2197 $Kernel::OM->Get('Kernel::System::Log')->Log( 2198 Priority => 'error', 2199 Message => "Need $Needed!" 2200 ); 2201 return; 2202 } 2203 } 2204 2205 # get current ticket 2206 my %Ticket = $Self->TicketGet( 2207 %Param, 2208 DynamicFields => 0, 2209 ); 2210 2211 # update needed? 2212 return 1 if $Param{ServiceID} eq $Ticket{ServiceID}; 2213 2214 # permission check 2215 my %ServiceList = $Self->TicketServiceList(%Param); 2216 if ( $Param{ServiceID} ne '' && !$ServiceList{ $Param{ServiceID} } ) { 2217 $Kernel::OM->Get('Kernel::System::Log')->Log( 2218 Priority => 'notice', 2219 Message => "Permission denied on TicketID: $Param{TicketID}!", 2220 ); 2221 return; 2222 } 2223 2224 # check database undef/NULL (set value to undef/NULL to prevent database errors) 2225 for my $Parameter (qw(ServiceID SLAID)) { 2226 if ( !$Param{$Parameter} ) { 2227 $Param{$Parameter} = undef; 2228 } 2229 } 2230 2231 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 2232 SQL => 'UPDATE ticket SET service_id = ?, change_time = current_timestamp, ' 2233 . ' change_by = ? WHERE id = ?', 2234 Bind => [ \$Param{ServiceID}, \$Param{UserID}, \$Param{TicketID} ], 2235 ); 2236 2237 # clear ticket cache 2238 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 2239 2240 # get new ticket data 2241 my %TicketNew = $Self->TicketGet( 2242 %Param, 2243 DynamicFields => 0, 2244 ); 2245 $TicketNew{Service} = $TicketNew{Service} || 'NULL'; 2246 $Param{ServiceID} = $Param{ServiceID} || ''; 2247 $Ticket{Service} = $Ticket{Service} || 'NULL'; 2248 $Ticket{ServiceID} = $Ticket{ServiceID} || ''; 2249 2250 # history insert 2251 $Self->HistoryAdd( 2252 TicketID => $Param{TicketID}, 2253 HistoryType => 'ServiceUpdate', 2254 Name => 2255 "\%\%$TicketNew{Service}\%\%$Param{ServiceID}\%\%$Ticket{Service}\%\%$Ticket{ServiceID}", 2256 CreateUserID => $Param{UserID}, 2257 ); 2258 2259 # trigger notification event 2260 $Self->EventHandler( 2261 Event => 'NotificationServiceUpdate', 2262 Data => { 2263 TicketID => $Param{TicketID}, 2264 CustomerMessageParams => {}, 2265 }, 2266 UserID => $Param{UserID}, 2267 ); 2268 2269 # trigger event 2270 $Self->EventHandler( 2271 Event => 'TicketServiceUpdate', 2272 Data => { 2273 TicketID => $Param{TicketID}, 2274 }, 2275 UserID => $Param{UserID}, 2276 ); 2277 2278 return 1; 2279} 2280 2281=head2 TicketEscalationPreferences() 2282 2283get escalation preferences of a ticket (e. g. from SLA or from Queue based settings) 2284 2285 my %Escalation = $TicketObject->TicketEscalationPreferences( 2286 Ticket => $Param{Ticket}, 2287 UserID => $Param{UserID}, 2288 ); 2289 2290=cut 2291 2292sub TicketEscalationPreferences { 2293 my ( $Self, %Param ) = @_; 2294 2295 # check needed stuff 2296 for my $Needed (qw(Ticket UserID)) { 2297 if ( !defined $Param{$Needed} ) { 2298 $Kernel::OM->Get('Kernel::System::Log')->Log( 2299 Priority => 'error', 2300 Message => "Need $Needed!" 2301 ); 2302 return; 2303 } 2304 } 2305 2306 # get ticket attributes 2307 my %Ticket = %{ $Param{Ticket} }; 2308 2309 # get escalation properties 2310 my %Escalation; 2311 if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service') && $Ticket{SLAID} ) { 2312 2313 %Escalation = $Kernel::OM->Get('Kernel::System::SLA')->SLAGet( 2314 SLAID => $Ticket{SLAID}, 2315 UserID => $Param{UserID}, 2316 Cache => 1, 2317 ); 2318 } 2319 else { 2320 %Escalation = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( 2321 ID => $Ticket{QueueID}, 2322 UserID => $Param{UserID}, 2323 Cache => 1, 2324 ); 2325 } 2326 2327 return %Escalation; 2328} 2329 2330=head2 TicketEscalationDateCalculation() 2331 2332get escalation properties of a ticket 2333 2334 my %Escalation = $TicketObject->TicketEscalationDateCalculation( 2335 Ticket => $Param{Ticket}, 2336 UserID => $Param{UserID}, 2337 ); 2338 2339returns 2340 2341 (general escalation info) 2342 EscalationDestinationIn (escalation in e. g. 1h 4m) 2343 EscalationDestinationTime (date of escalation in unix time, e. g. 72193292) 2344 EscalationDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00") 2345 EscalationTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800") 2346 EscalationTime (seconds total till escalation, e. g. "3600") 2347 2348 (detail escalation info about first response, update and solution time) 2349 FirstResponseTimeEscalation (if true, ticket is escalated) 2350 FirstResponseTimeNotification (if true, notify - x% of escalation has reached) 2351 FirstResponseTimeDestinationTime (date of escalation in unix time, e. g. 72193292) 2352 FirstResponseTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00") 2353 FirstResponseTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800") 2354 FirstResponseTime (seconds total till escalation, e. g. "3600") 2355 2356 UpdateTimeEscalation (if true, ticket is escalated) 2357 UpdateTimeNotification (if true, notify - x% of escalation has reached) 2358 UpdateTimeDestinationTime (date of escalation in unix time, e. g. 72193292) 2359 UpdateTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00") 2360 UpdateTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800") 2361 UpdateTime (seconds total till escalation, e. g. "3600") 2362 2363 SolutionTimeEscalation (if true, ticket is escalated) 2364 SolutionTimeNotification (if true, notify - x% of escalation has reached) 2365 SolutionTimeDestinationTime (date of escalation in unix time, e. g. 72193292) 2366 SolutionTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00") 2367 SolutionTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800") 2368 SolutionTime (seconds total till escalation, e. g. "3600") 2369 2370=cut 2371 2372sub TicketEscalationDateCalculation { 2373 my ( $Self, %Param ) = @_; 2374 2375 # check needed stuff 2376 for my $Needed (qw(Ticket UserID)) { 2377 if ( !defined $Param{$Needed} ) { 2378 $Kernel::OM->Get('Kernel::System::Log')->Log( 2379 Priority => 'error', 2380 Message => "Need $Needed!" 2381 ); 2382 return; 2383 } 2384 } 2385 2386 # get ticket attributes 2387 my %Ticket = %{ $Param{Ticket} }; 2388 2389 # do no escalations on (merge|close|remove) tickets 2390 return if $Ticket{StateType} eq 'merged'; 2391 return if $Ticket{StateType} eq 'closed'; 2392 return if $Ticket{StateType} eq 'removed'; 2393 2394 # get escalation properties 2395 my %Escalation = $Self->TicketEscalationPreferences( 2396 Ticket => $Param{Ticket}, 2397 UserID => $Param{UserID} || 1, 2398 ); 2399 2400 # return if we do not have any escalation attributes 2401 my %Map = ( 2402 EscalationResponseTime => 'FirstResponse', 2403 EscalationUpdateTime => 'Update', 2404 EscalationSolutionTime => 'Solution', 2405 ); 2406 my $EscalationAttribute; 2407 KEY: 2408 for my $Key ( sort keys %Map ) { 2409 if ( $Escalation{ $Map{$Key} . 'Time' } ) { 2410 $EscalationAttribute = 1; 2411 last KEY; 2412 } 2413 } 2414 2415 return if !$EscalationAttribute; 2416 2417 # create datetime object 2418 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 2419 2420 # calculate escalation times based on escalation properties 2421 my %Data; 2422 2423 TIME: 2424 for my $Key ( sort keys %Map ) { 2425 2426 next TIME if !$Ticket{$Key}; 2427 2428 # get time before or over escalation (escalation_destination_unixtime - now) 2429 my $TimeTillEscalation = $Ticket{$Key} - $DateTimeObject->ToEpoch(); 2430 2431 # ticket is not escalated till now ($TimeTillEscalation > 0) 2432 my $WorkingTime = 0; 2433 if ( $TimeTillEscalation > 0 ) { 2434 2435 my $StopTimeObj = $Kernel::OM->Create( 2436 'Kernel::System::DateTime', 2437 ObjectParams => { 2438 Epoch => $Ticket{$Key} 2439 } 2440 ); 2441 2442 my $DeltaObj = $DateTimeObject->Delta( 2443 DateTimeObject => $StopTimeObj, 2444 ForWorkingTime => 1, 2445 Calendar => $Escalation{Calendar}, 2446 ); 2447 2448 $WorkingTime = $DeltaObj ? $DeltaObj->{AbsoluteSeconds} : 0; 2449 2450 # extract needed data 2451 my $Notify = $Escalation{ $Map{$Key} . 'Notify' }; 2452 my $Time = $Escalation{ $Map{$Key} . 'Time' }; 2453 2454 # set notification if notify % is reached 2455 if ( $Notify && $Time ) { 2456 2457 my $Reached = 100 - ( $WorkingTime / ( $Time * 60 / 100 ) ); 2458 2459 if ( $Reached >= $Notify ) { 2460 $Data{ $Map{$Key} . 'TimeNotification' } = 1; 2461 } 2462 } 2463 } 2464 2465 # ticket is overtime ($TimeTillEscalation < 0) 2466 else { 2467 my $StartTimeObj = $Kernel::OM->Create( 2468 'Kernel::System::DateTime', 2469 ObjectParams => { 2470 Epoch => $Ticket{$Key} 2471 } 2472 ); 2473 2474 my $DeltaObj = $StartTimeObj->Delta( 2475 DateTimeObject => $DateTimeObject, 2476 ForWorkingTime => 1, 2477 Calendar => $Escalation{Calendar}, 2478 ); 2479 2480 $WorkingTime = 0; 2481 if ( $DeltaObj && $DeltaObj->{AbsoluteSeconds} ) { 2482 $WorkingTime = '-' . $DeltaObj->{AbsoluteSeconds}; 2483 } 2484 2485 # set escalation 2486 $Data{ $Map{$Key} . 'TimeEscalation' } = 1; 2487 } 2488 2489 my $DestinationDate = $Kernel::OM->Create( 2490 'Kernel::System::DateTime', 2491 ObjectParams => { 2492 Epoch => $Ticket{$Key} 2493 } 2494 ); 2495 2496 $Data{ $Map{$Key} . 'TimeDestinationTime' } = $Ticket{$Key}; 2497 $Data{ $Map{$Key} . 'TimeDestinationDate' } = $DestinationDate->ToString(); 2498 $Data{ $Map{$Key} . 'TimeWorkingTime' } = $WorkingTime; 2499 $Data{ $Map{$Key} . 'Time' } = $TimeTillEscalation; 2500 2501 # set global escalation attributes (set the escalation which is the first in time) 2502 if ( 2503 !$Data{EscalationDestinationTime} 2504 || $Data{EscalationDestinationTime} > $Ticket{$Key} 2505 ) 2506 { 2507 $Data{EscalationDestinationTime} = $Ticket{$Key}; 2508 $Data{EscalationDestinationDate} = $DestinationDate->ToString(); 2509 $Data{EscalationTimeWorkingTime} = $WorkingTime; 2510 $Data{EscalationTime} = $TimeTillEscalation; 2511 2512 # escalation time in readable way 2513 $Data{EscalationDestinationIn} = ''; 2514 $WorkingTime = abs($WorkingTime); 2515 if ( $WorkingTime >= 3600 ) { 2516 $Data{EscalationDestinationIn} .= int( $WorkingTime / 3600 ) . 'h '; 2517 $WorkingTime = $WorkingTime 2518 - ( int( $WorkingTime / 3600 ) * 3600 ); # remove already shown hours 2519 } 2520 if ( $WorkingTime <= 3600 || int( $WorkingTime / 60 ) ) { 2521 $Data{EscalationDestinationIn} .= int( $WorkingTime / 60 ) . 'm'; 2522 } 2523 } 2524 } 2525 2526 return %Data; 2527} 2528 2529=head2 TicketEscalationIndexBuild() 2530 2531build escalation index of one ticket with current settings (SLA, Queue, Calendar...) 2532 2533 my $Success = $TicketObject->TicketEscalationIndexBuild( 2534 TicketID => $Param{TicketID}, 2535 UserID => $Param{UserID}, 2536 ); 2537 2538=cut 2539 2540sub TicketEscalationIndexBuild { 2541 my ( $Self, %Param ) = @_; 2542 2543 # check needed stuff 2544 for my $Needed (qw(TicketID UserID)) { 2545 if ( !defined $Param{$Needed} ) { 2546 $Kernel::OM->Get('Kernel::System::Log')->Log( 2547 Priority => 'error', 2548 Message => "Need $Needed!", 2549 ); 2550 return; 2551 } 2552 } 2553 2554 my %Ticket = $Self->TicketGet( 2555 TicketID => $Param{TicketID}, 2556 UserID => $Param{UserID}, 2557 DynamicFields => 0, 2558 Silent => 1, # Suppress warning if the ticket was deleted in the meantime. 2559 ); 2560 2561 return if !%Ticket; 2562 2563 # get database object 2564 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2565 2566 # do no escalations on (merge|close|remove) tickets 2567 if ( $Ticket{StateType} && $Ticket{StateType} =~ /^(merge|close|remove)/i ) { 2568 2569 # update escalation times with 0 2570 my %EscalationTimes = ( 2571 EscalationTime => 'escalation_time', 2572 EscalationResponseTime => 'escalation_response_time', 2573 EscalationUpdateTime => 'escalation_update_time', 2574 EscalationSolutionTime => 'escalation_solution_time', 2575 ); 2576 2577 TIME: 2578 for my $Key ( sort keys %EscalationTimes ) { 2579 2580 # check if table update is needed 2581 next TIME if !$Ticket{$Key}; 2582 2583 # update ticket table 2584 $DBObject->Do( 2585 SQL => 2586 "UPDATE ticket SET $EscalationTimes{$Key} = 0, change_time = current_timestamp, " 2587 . " change_by = ? WHERE id = ?", 2588 Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ], 2589 ); 2590 } 2591 2592 # clear ticket cache 2593 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 2594 2595 return 1; 2596 } 2597 2598 # get escalation properties 2599 my %Escalation; 2600 if (%Ticket) { 2601 %Escalation = $Self->TicketEscalationPreferences( 2602 Ticket => \%Ticket, 2603 UserID => $Param{UserID}, 2604 ); 2605 } 2606 2607 # find escalation times 2608 my $EscalationTime = 0; 2609 2610 # update first response (if not responded till now) 2611 if ( !$Escalation{FirstResponseTime} ) { 2612 $DBObject->Do( 2613 SQL => 2614 'UPDATE ticket SET escalation_response_time = 0, change_time = current_timestamp, ' 2615 . ' change_by = ? WHERE id = ?', 2616 Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ] 2617 ); 2618 } 2619 else { 2620 2621 # check if first response is already done 2622 my %FirstResponseDone = $Self->_TicketGetFirstResponse( 2623 TicketID => $Ticket{TicketID}, 2624 Ticket => \%Ticket, 2625 ); 2626 2627 # update first response time to 0 2628 if (%FirstResponseDone) { 2629 $DBObject->Do( 2630 SQL => 2631 'UPDATE ticket SET escalation_response_time = 0, change_time = current_timestamp, ' 2632 . ' change_by = ? WHERE id = ?', 2633 Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ] 2634 ); 2635 } 2636 2637 # update first response time to expected escalation destination time 2638 else { 2639 2640 my $DateTimeObject = $Kernel::OM->Create( 2641 'Kernel::System::DateTime', 2642 ObjectParams => { 2643 String => $Ticket{Created}, 2644 } 2645 ); 2646 2647 $DateTimeObject->Add( 2648 AsWorkingTime => 1, 2649 Calendar => $Escalation{Calendar}, 2650 Seconds => $Escalation{FirstResponseTime} * 60, 2651 ); 2652 2653 my $DestinationTime = $DateTimeObject->ToEpoch(); 2654 2655 # update first response time to $DestinationTime 2656 $DBObject->Do( 2657 SQL => 2658 'UPDATE ticket SET escalation_response_time = ?, change_time = current_timestamp, ' 2659 . ' change_by = ? WHERE id = ?', 2660 Bind => [ \$DestinationTime, \$Param{UserID}, \$Ticket{TicketID}, ] 2661 ); 2662 2663 # remember escalation time 2664 $EscalationTime = $DestinationTime; 2665 } 2666 } 2667 2668 # update update && do not escalate in "pending auto" for escalation update time 2669 if ( !$Escalation{UpdateTime} || $Ticket{StateType} =~ /^(pending)/i ) { 2670 $DBObject->Do( 2671 SQL => 'UPDATE ticket SET escalation_update_time = 0, change_time = current_timestamp, ' 2672 . ' change_by = ? WHERE id = ?', 2673 Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ] 2674 ); 2675 } 2676 else { 2677 2678 # check if update escalation should be set 2679 my @SenderHistory; 2680 return if !$DBObject->Prepare( 2681 SQL => 'SELECT article_sender_type_id, is_visible_for_customer, create_time FROM ' 2682 . 'article WHERE ticket_id = ? ORDER BY create_time ASC', 2683 Bind => [ \$Param{TicketID} ], 2684 ); 2685 while ( my @Row = $DBObject->FetchrowArray() ) { 2686 push @SenderHistory, { 2687 SenderTypeID => $Row[0], 2688 IsVisibleForCustomer => $Row[1], 2689 Created => $Row[2], 2690 }; 2691 } 2692 2693 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 2694 2695 # fill up lookups 2696 for my $Row (@SenderHistory) { 2697 $Row->{SenderType} = $ArticleObject->ArticleSenderTypeLookup( 2698 SenderTypeID => $Row->{SenderTypeID}, 2699 ); 2700 } 2701 2702 # get latest customer contact time 2703 my $LastSenderTime; 2704 my $LastSenderType = ''; 2705 ROW: 2706 for my $Row ( reverse @SenderHistory ) { 2707 2708 # fill up latest sender time (as initial value) 2709 if ( !$LastSenderTime ) { 2710 $LastSenderTime = $Row->{Created}; 2711 } 2712 2713 # do not use locked tickets for calculation 2714 #last ROW if $Ticket{Lock} eq 'lock'; 2715 2716 # do not use internal articles for calculation 2717 next ROW if !$Row->{IsVisibleForCustomer}; 2718 2719 # only use 'agent' and 'customer' sender types for calculation 2720 next ROW if $Row->{SenderType} !~ /^(agent|customer)$/; 2721 2722 # last ROW if latest was customer and the next was not customer 2723 # otherwise use also next, older customer article as latest 2724 # customer followup for starting escalation 2725 if ( $Row->{SenderType} eq 'agent' && $LastSenderType eq 'customer' ) { 2726 last ROW; 2727 } 2728 2729 # start escalation on latest customer article 2730 if ( $Row->{SenderType} eq 'customer' ) { 2731 $LastSenderType = 'customer'; 2732 $LastSenderTime = $Row->{Created}; 2733 } 2734 2735 # start escalation on latest agent article 2736 if ( $Row->{SenderType} eq 'agent' ) { 2737 $LastSenderTime = $Row->{Created}; 2738 last ROW; 2739 } 2740 } 2741 if ($LastSenderTime) { 2742 2743 # create datetime object 2744 my $DateTimeObject = $Kernel::OM->Create( 2745 'Kernel::System::DateTime', 2746 ObjectParams => { 2747 String => $LastSenderTime, 2748 } 2749 ); 2750 2751 $DateTimeObject->Add( 2752 Seconds => $Escalation{UpdateTime} * 60, 2753 AsWorkingTime => 1, 2754 Calendar => $Escalation{Calendar}, 2755 ); 2756 2757 my $DestinationTime = $DateTimeObject->ToEpoch(); 2758 2759 # update update time to $DestinationTime 2760 $DBObject->Do( 2761 SQL => 2762 'UPDATE ticket SET escalation_update_time = ?, change_time = current_timestamp, ' 2763 . ' change_by = ? WHERE id = ?', 2764 Bind => [ \$DestinationTime, \$Param{UserID}, \$Ticket{TicketID}, ] 2765 ); 2766 2767 # remember escalation time 2768 if ( $EscalationTime == 0 || $DestinationTime < $EscalationTime ) { 2769 $EscalationTime = $DestinationTime; 2770 } 2771 } 2772 2773 # else, no not escalate, because latest sender was agent 2774 else { 2775 $DBObject->Do( 2776 SQL => 2777 'UPDATE ticket SET escalation_update_time = 0, change_time = current_timestamp, ' 2778 . ' change_by = ? WHERE id = ?', 2779 Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ] 2780 ); 2781 } 2782 } 2783 2784 # update solution 2785 if ( !$Escalation{SolutionTime} ) { 2786 $DBObject->Do( 2787 SQL => 2788 'UPDATE ticket SET escalation_solution_time = 0, change_time = current_timestamp, ' 2789 . ' change_by = ? WHERE id = ?', 2790 Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ], 2791 ); 2792 } 2793 else { 2794 2795 # find solution time / first close time 2796 my %SolutionDone = $Self->_TicketGetClosed( 2797 TicketID => $Ticket{TicketID}, 2798 Ticket => \%Ticket, 2799 ); 2800 2801 # update solution time to 0 2802 if (%SolutionDone) { 2803 $DBObject->Do( 2804 SQL => 2805 'UPDATE ticket SET escalation_solution_time = 0, change_time = current_timestamp, ' 2806 . ' change_by = ? WHERE id = ?', 2807 Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ], 2808 ); 2809 } 2810 else { 2811 2812 # get datetime object 2813 my $DateTimeObject = $Kernel::OM->Create( 2814 'Kernel::System::DateTime', 2815 ObjectParams => { 2816 String => $Ticket{Created}, 2817 } 2818 ); 2819 2820 $DateTimeObject->Add( 2821 Seconds => $Escalation{SolutionTime} * 60, 2822 AsWorkingTime => 1, 2823 Calendar => $Escalation{Calendar}, 2824 ); 2825 2826 my $DestinationTime = $DateTimeObject->ToEpoch(); 2827 2828 # update solution time to $DestinationTime 2829 $DBObject->Do( 2830 SQL => 2831 'UPDATE ticket SET escalation_solution_time = ?, change_time = current_timestamp, ' 2832 . ' change_by = ? WHERE id = ?', 2833 Bind => [ \$DestinationTime, \$Param{UserID}, \$Ticket{TicketID}, ], 2834 ); 2835 2836 # remember escalation time 2837 if ( $EscalationTime == 0 || $DestinationTime < $EscalationTime ) { 2838 $EscalationTime = $DestinationTime; 2839 } 2840 } 2841 } 2842 2843 # update escalation time (< escalation time) 2844 if ( defined $EscalationTime ) { 2845 $DBObject->Do( 2846 SQL => 'UPDATE ticket SET escalation_time = ?, change_time = current_timestamp, ' 2847 . ' change_by = ? WHERE id = ?', 2848 Bind => [ \$EscalationTime, \$Param{UserID}, \$Ticket{TicketID}, ], 2849 ); 2850 } 2851 2852 # clear ticket cache 2853 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 2854 2855 return 1; 2856} 2857 2858=head2 TicketSLAList() 2859 2860to get all possible SLAs for a ticket (depends on workflow, if configured) 2861 2862 my %SLAs = $TicketObject->TicketSLAList( 2863 ServiceID => 1, 2864 UserID => 123, 2865 ); 2866 2867 my %SLAs = $TicketObject->TicketSLAList( 2868 ServiceID => 1, 2869 CustomerUserID => 'customer_user_id_123', 2870 ); 2871 2872 2873 my %SLAs = $TicketObject->TicketSLAList( 2874 QueueID => 123, 2875 ServiceID => 1, 2876 UserID => 123, 2877 ); 2878 2879 my %SLAs = $TicketObject->TicketSLAList( 2880 TicketID => 123, 2881 ServiceID => 1, 2882 UserID => 123, 2883 ); 2884 2885Returns: 2886 2887 %SLAs = ( 2888 1 => 'SLA A', 2889 2 => 'SLA B', 2890 3 => 'SLA C', 2891 ); 2892 2893=cut 2894 2895sub TicketSLAList { 2896 my ( $Self, %Param ) = @_; 2897 2898 # check needed stuff 2899 if ( !$Param{UserID} && !$Param{CustomerUserID} ) { 2900 $Kernel::OM->Get('Kernel::System::Log')->Log( 2901 Priority => 'error', 2902 Message => 'Need UserID or CustomerUserID!' 2903 ); 2904 return; 2905 } 2906 2907 # check needed stuff 2908 if ( !$Param{QueueID} && !$Param{TicketID} ) { 2909 $Kernel::OM->Get('Kernel::System::Log')->Log( 2910 Priority => 'error', 2911 Message => 'Need QueueID or TicketID!' 2912 ); 2913 return; 2914 } 2915 2916 # return emty hash, if no service id is given 2917 if ( !$Param{ServiceID} ) { 2918 return (); 2919 } 2920 2921 # get sla list 2922 my %SLAs = $Kernel::OM->Get('Kernel::System::SLA')->SLAList( 2923 ServiceID => $Param{ServiceID}, 2924 UserID => 1, 2925 ); 2926 2927 # workflow 2928 my $ACL = $Self->TicketAcl( 2929 %Param, 2930 ReturnType => 'Ticket', 2931 ReturnSubType => 'SLA', 2932 Data => \%SLAs, 2933 ); 2934 2935 return $Self->TicketAclData() if $ACL; 2936 return %SLAs; 2937} 2938 2939=head2 TicketSLASet() 2940 2941to set a ticket service level agreement 2942 2943 my $Success = $TicketObject->TicketSLASet( 2944 SLAID => 123, 2945 TicketID => 123, 2946 UserID => 123, 2947 ); 2948 2949 my $Success = $TicketObject->TicketSLASet( 2950 SLA => 'SLA A', 2951 TicketID => 123, 2952 UserID => 123, 2953 ); 2954 2955Events: 2956 TicketSLAUpdate 2957 2958=cut 2959 2960sub TicketSLASet { 2961 my ( $Self, %Param ) = @_; 2962 2963 # sla lookup 2964 if ( $Param{SLA} && !$Param{SLAID} ) { 2965 $Param{SLAID} = $Kernel::OM->Get('Kernel::System::SLA')->SLALookup( Name => $Param{SLA} ); 2966 } 2967 2968 # check needed stuff 2969 for my $Needed (qw(TicketID SLAID UserID)) { 2970 if ( !defined $Param{$Needed} ) { 2971 $Kernel::OM->Get('Kernel::System::Log')->Log( 2972 Priority => 'error', 2973 Message => "Need $Needed!" 2974 ); 2975 return; 2976 } 2977 } 2978 2979 # get current ticket 2980 my %Ticket = $Self->TicketGet( 2981 %Param, 2982 DynamicFields => 0, 2983 ); 2984 2985 # update needed? 2986 return 1 if ( $Param{SLAID} eq $Ticket{SLAID} ); 2987 2988 # permission check 2989 my %SLAList = $Self->TicketSLAList( 2990 %Param, 2991 ServiceID => $Ticket{ServiceID}, 2992 ); 2993 2994 if ( $Param{UserID} != 1 && $Param{SLAID} ne '' && !$SLAList{ $Param{SLAID} } ) { 2995 $Kernel::OM->Get('Kernel::System::Log')->Log( 2996 Priority => 'notice', 2997 Message => "Permission denied on TicketID: $Param{TicketID}!", 2998 ); 2999 return; 3000 } 3001 3002 # check database undef/NULL (set value to undef/NULL to prevent database errors) 3003 for my $Parameter (qw(ServiceID SLAID)) { 3004 if ( !$Param{$Parameter} ) { 3005 $Param{$Parameter} = undef; 3006 } 3007 } 3008 3009 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 3010 SQL => 'UPDATE ticket SET sla_id = ?, change_time = current_timestamp, ' 3011 . ' change_by = ? WHERE id = ?', 3012 Bind => [ \$Param{SLAID}, \$Param{UserID}, \$Param{TicketID} ], 3013 ); 3014 3015 # clear ticket cache 3016 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 3017 3018 # get new ticket data 3019 my %TicketNew = $Self->TicketGet( 3020 %Param, 3021 DynamicFields => 0, 3022 ); 3023 $TicketNew{SLA} = $TicketNew{SLA} || 'NULL'; 3024 $Param{SLAID} = $Param{SLAID} || ''; 3025 $Ticket{SLA} = $Ticket{SLA} || 'NULL'; 3026 $Ticket{SLAID} = $Ticket{SLAID} || ''; 3027 3028 # history insert 3029 $Self->HistoryAdd( 3030 TicketID => $Param{TicketID}, 3031 HistoryType => 'SLAUpdate', 3032 Name => "\%\%$TicketNew{SLA}\%\%$Param{SLAID}\%\%$Ticket{SLA}\%\%$Ticket{SLAID}", 3033 CreateUserID => $Param{UserID}, 3034 ); 3035 3036 # trigger event, OldTicketData is needed for escalation events 3037 $Self->EventHandler( 3038 Event => 'TicketSLAUpdate', 3039 Data => { 3040 TicketID => $Param{TicketID}, 3041 OldTicketData => \%Ticket, 3042 }, 3043 UserID => $Param{UserID}, 3044 ); 3045 3046 return 1; 3047} 3048 3049=head2 TicketCustomerSet() 3050 3051Set customer data of ticket. Can set 'No' (CustomerID), 3052'User' (CustomerUserID), or both. 3053 3054 my $Success = $TicketObject->TicketCustomerSet( 3055 No => 'client123', 3056 User => 'client-user-123', 3057 TicketID => 123, 3058 UserID => 23, 3059 ); 3060 3061Events: 3062 TicketCustomerUpdate 3063 3064=cut 3065 3066sub TicketCustomerSet { 3067 my ( $Self, %Param ) = @_; 3068 3069 # check needed stuff 3070 for my $Needed (qw(TicketID UserID)) { 3071 if ( !$Param{$Needed} ) { 3072 $Kernel::OM->Get('Kernel::System::Log')->Log( 3073 Priority => 'error', 3074 Message => "Need $Needed!" 3075 ); 3076 return; 3077 } 3078 } 3079 if ( !defined $Param{No} && !defined $Param{User} ) { 3080 $Kernel::OM->Get('Kernel::System::Log')->Log( 3081 Priority => 'error', 3082 Message => 'Need User or No for update!' 3083 ); 3084 return; 3085 } 3086 3087 # get database object 3088 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 3089 3090 # db customer id update 3091 if ( defined $Param{No} ) { 3092 3093 my $Ok = $DBObject->Do( 3094 SQL => 'UPDATE ticket SET customer_id = ?, ' 3095 . ' change_time = current_timestamp, change_by = ? WHERE id = ?', 3096 Bind => [ \$Param{No}, \$Param{UserID}, \$Param{TicketID} ] 3097 ); 3098 3099 if ($Ok) { 3100 $Param{History} = "CustomerID=$Param{No};"; 3101 } 3102 } 3103 3104 # db customer user update 3105 if ( defined $Param{User} ) { 3106 3107 my $Ok = $DBObject->Do( 3108 SQL => 'UPDATE ticket SET customer_user_id = ?, ' 3109 . 'change_time = current_timestamp, change_by = ? WHERE id = ?', 3110 Bind => [ \$Param{User}, \$Param{UserID}, \$Param{TicketID} ], 3111 ); 3112 3113 if ($Ok) { 3114 $Param{History} .= "CustomerUser=$Param{User};"; 3115 } 3116 } 3117 3118 # clear ticket cache 3119 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 3120 3121 # if no change 3122 if ( !$Param{History} ) { 3123 return; 3124 } 3125 3126 # history insert 3127 $Self->HistoryAdd( 3128 TicketID => $Param{TicketID}, 3129 HistoryType => 'CustomerUpdate', 3130 Name => "\%\%" . $Param{History}, 3131 CreateUserID => $Param{UserID}, 3132 ); 3133 3134 # trigger event 3135 $Self->EventHandler( 3136 Event => 'TicketCustomerUpdate', 3137 Data => { 3138 TicketID => $Param{TicketID}, 3139 }, 3140 UserID => $Param{UserID}, 3141 ); 3142 3143 return 1; 3144} 3145 3146=head2 TicketPermission() 3147 3148returns whether or not the agent has permission on a ticket 3149 3150 my $Access = $TicketObject->TicketPermission( 3151 Type => 'ro', 3152 TicketID => 123, 3153 UserID => 123, 3154 ); 3155 3156or without logging, for example for to check if a link/action should be shown 3157 3158 my $Access = $TicketObject->TicketPermission( 3159 Type => 'ro', 3160 TicketID => 123, 3161 LogNo => 1, 3162 UserID => 123, 3163 ); 3164 3165=cut 3166 3167sub TicketPermission { 3168 my ( $Self, %Param ) = @_; 3169 3170 # check needed stuff 3171 for my $Needed (qw(Type TicketID UserID)) { 3172 if ( !$Param{$Needed} ) { 3173 $Kernel::OM->Get('Kernel::System::Log')->Log( 3174 Priority => 'error', 3175 Message => "Need $Needed!" 3176 ); 3177 return; 3178 } 3179 } 3180 3181 # get needed objects 3182 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 3183 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 3184 3185 # run all TicketPermission modules 3186 if ( ref $ConfigObject->Get('Ticket::Permission') eq 'HASH' ) { 3187 my %Modules = %{ $ConfigObject->Get('Ticket::Permission') }; 3188 3189 MODULE: 3190 for my $Module ( sort keys %Modules ) { 3191 3192 # log try of load module 3193 if ( $Self->{Debug} > 1 ) { 3194 $Kernel::OM->Get('Kernel::System::Log')->Log( 3195 Priority => 'debug', 3196 Message => "Try to load module: $Modules{$Module}->{Module}!", 3197 ); 3198 } 3199 3200 # load module 3201 next MODULE if !$MainObject->Require( $Modules{$Module}->{Module} ); 3202 3203 # create object 3204 my $ModuleObject = $Modules{$Module}->{Module}->new(); 3205 3206 # execute Run() 3207 my $AccessOk = $ModuleObject->Run(%Param); 3208 3209 # check granted option (should I say ok) 3210 if ( $AccessOk && $Modules{$Module}->{Granted} ) { 3211 if ( $Self->{Debug} > 0 ) { 3212 $Kernel::OM->Get('Kernel::System::Log')->Log( 3213 Priority => 'debug', 3214 Message => "Granted access '$Param{Type}' true for " 3215 . "TicketID '$Param{TicketID}' " 3216 . "through $Modules{$Module}->{Module} (no more checks)!", 3217 ); 3218 } 3219 3220 # access ok 3221 return 1; 3222 } 3223 3224 # return because access is false but it's required 3225 if ( !$AccessOk && $Modules{$Module}->{Required} ) { 3226 if ( !$Param{LogNo} ) { 3227 $Kernel::OM->Get('Kernel::System::Log')->Log( 3228 Priority => 'notice', 3229 Message => "Permission denied because module " 3230 . "($Modules{$Module}->{Module}) is required " 3231 . "(UserID: $Param{UserID} '$Param{Type}' on " 3232 . "TicketID: $Param{TicketID})!", 3233 ); 3234 } 3235 3236 # access not ok 3237 return; 3238 } 3239 } 3240 } 3241 3242 # don't grant access to the ticket 3243 if ( !$Param{LogNo} ) { 3244 $Kernel::OM->Get('Kernel::System::Log')->Log( 3245 Priority => 'notice', 3246 Message => "Permission denied (UserID: $Param{UserID} '$Param{Type}' " 3247 . "on TicketID: $Param{TicketID})!", 3248 ); 3249 } 3250 3251 return; 3252} 3253 3254=head2 TicketCustomerPermission() 3255 3256returns whether or not a customer has permission to a ticket 3257 3258 my $Access = $TicketObject->TicketCustomerPermission( 3259 Type => 'ro', 3260 TicketID => 123, 3261 UserID => 123, 3262 ); 3263 3264or without logging, for example for to check if a link/action should be displayed 3265 3266 my $Access = $TicketObject->TicketCustomerPermission( 3267 Type => 'ro', 3268 TicketID => 123, 3269 LogNo => 1, 3270 UserID => 123, 3271 ); 3272 3273=cut 3274 3275sub TicketCustomerPermission { 3276 my ( $Self, %Param ) = @_; 3277 3278 # check needed stuff 3279 for my $Needed (qw(Type TicketID UserID)) { 3280 if ( !$Param{$Needed} ) { 3281 $Kernel::OM->Get('Kernel::System::Log')->Log( 3282 Priority => 'error', 3283 Message => "Need $Needed!" 3284 ); 3285 return; 3286 } 3287 } 3288 3289 # get main object 3290 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 3291 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 3292 3293 # run all CustomerTicketPermission modules 3294 if ( ref $ConfigObject->Get('CustomerTicket::Permission') eq 'HASH' ) { 3295 my %Modules = %{ $ConfigObject->Get('CustomerTicket::Permission') }; 3296 3297 MODULE: 3298 for my $Module ( sort keys %Modules ) { 3299 3300 # log try of load module 3301 if ( $Self->{Debug} > 1 ) { 3302 $Kernel::OM->Get('Kernel::System::Log')->Log( 3303 Priority => 'debug', 3304 Message => "Try to load module: $Modules{$Module}->{Module}!", 3305 ); 3306 } 3307 3308 # load module 3309 next MODULE if !$MainObject->Require( $Modules{$Module}->{Module} ); 3310 3311 # create object 3312 my $ModuleObject = $Modules{$Module}->{Module}->new(); 3313 3314 # execute Run() 3315 my $AccessOk = $ModuleObject->Run(%Param); 3316 3317 # check granted option (should I say ok) 3318 if ( $AccessOk && $Modules{$Module}->{Granted} ) { 3319 if ( $Self->{Debug} > 0 ) { 3320 $Kernel::OM->Get('Kernel::System::Log')->Log( 3321 Priority => 'debug', 3322 Message => "Granted access '$Param{Type}' true for " 3323 . "TicketID '$Param{TicketID}' " 3324 . "through $Modules{$Module}->{Module} (no more checks)!", 3325 ); 3326 } 3327 3328 # access ok 3329 return 1; 3330 } 3331 3332 # return because access is false but it's required 3333 if ( !$AccessOk && $Modules{$Module}->{Required} ) { 3334 if ( !$Param{LogNo} ) { 3335 $Kernel::OM->Get('Kernel::System::Log')->Log( 3336 Priority => 'notice', 3337 Message => "Permission denied because module " 3338 . "($Modules{$Module}->{Module}) is required " 3339 . "(UserID: $Param{UserID} '$Param{Type}' on " 3340 . "TicketID: $Param{TicketID})!", 3341 ); 3342 } 3343 3344 # access not ok 3345 return; 3346 } 3347 } 3348 } 3349 3350 # don't grant access to the ticket 3351 if ( !$Param{LogNo} ) { 3352 $Kernel::OM->Get('Kernel::System::Log')->Log( 3353 Priority => 'notice', 3354 Message => "Permission denied (UserID: $Param{UserID} '$Param{Type}' on " 3355 . "TicketID: $Param{TicketID})!", 3356 ); 3357 } 3358 return; 3359} 3360 3361=head2 GetSubscribedUserIDsByQueueID() 3362 3363returns an array of user ids which selected the given queue id as 3364custom queue. 3365 3366 my @UserIDs = $TicketObject->GetSubscribedUserIDsByQueueID( 3367 QueueID => 123, 3368 ); 3369 3370Returns: 3371 3372 @UserIDs = ( 1, 2, 3 ); 3373 3374=cut 3375 3376sub GetSubscribedUserIDsByQueueID { 3377 my ( $Self, %Param ) = @_; 3378 3379 # check needed stuff 3380 if ( !$Param{QueueID} ) { 3381 $Kernel::OM->Get('Kernel::System::Log')->Log( 3382 Priority => 'error', 3383 Message => 'Need QueueID!' 3384 ); 3385 return; 3386 } 3387 3388 # get group of queue 3389 my %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( ID => $Param{QueueID} ); 3390 3391 # get database object 3392 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 3393 3394 # fetch all queues 3395 my @UserIDs; 3396 return if !$DBObject->Prepare( 3397 SQL => 'SELECT user_id FROM personal_queues WHERE queue_id = ?', 3398 Bind => [ \$Param{QueueID} ], 3399 ); 3400 while ( my @Row = $DBObject->FetchrowArray() ) { 3401 push @UserIDs, $Row[0]; 3402 } 3403 3404 # get needed objects 3405 my $GroupObject = $Kernel::OM->Get('Kernel::System::Group'); 3406 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 3407 3408 # check if user is valid and check permissions 3409 my @CleanUserIDs; 3410 3411 USER: 3412 for my $UserID (@UserIDs) { 3413 3414 my %User = $UserObject->GetUserData( 3415 UserID => $UserID, 3416 Valid => 1 3417 ); 3418 3419 next USER if !%User; 3420 3421 # just send emails to permitted agents 3422 my %GroupMember = $GroupObject->PermissionUserGet( 3423 UserID => $UserID, 3424 Type => 'ro', 3425 ); 3426 3427 if ( $GroupMember{ $Queue{GroupID} } ) { 3428 push @CleanUserIDs, $UserID; 3429 } 3430 } 3431 3432 return @CleanUserIDs; 3433} 3434 3435=head2 GetSubscribedUserIDsByServiceID() 3436 3437returns an array of user ids which selected the given service id as 3438custom service. 3439 3440 my @UserIDs = $TicketObject->GetSubscribedUserIDsByServiceID( 3441 ServiceID => 123, 3442 ); 3443 3444Returns: 3445 3446 @UserIDs = ( 1, 2, 3 ); 3447 3448=cut 3449 3450sub GetSubscribedUserIDsByServiceID { 3451 my ( $Self, %Param ) = @_; 3452 3453 # check needed stuff 3454 if ( !$Param{ServiceID} ) { 3455 $Kernel::OM->Get('Kernel::System::Log')->Log( 3456 Priority => 'error', 3457 Message => 'Need ServiceID!' 3458 ); 3459 return; 3460 } 3461 3462 # get database object 3463 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 3464 3465 # fetch all users 3466 my @UserIDs; 3467 return if !$DBObject->Prepare( 3468 SQL => ' 3469 SELECT user_id 3470 FROM personal_services 3471 WHERE service_id = ?', 3472 Bind => [ \$Param{ServiceID} ], 3473 ); 3474 3475 while ( my @Row = $DBObject->FetchrowArray() ) { 3476 push @UserIDs, $Row[0]; 3477 } 3478 3479 # get user object 3480 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 3481 3482 # check if user is valid 3483 my @CleanUserIDs; 3484 USER: 3485 for my $UserID (@UserIDs) { 3486 3487 my %User = $UserObject->GetUserData( 3488 UserID => $UserID, 3489 Valid => 1, 3490 ); 3491 3492 next USER if !%User; 3493 3494 push @CleanUserIDs, $UserID; 3495 } 3496 3497 return @CleanUserIDs; 3498} 3499 3500=head2 TicketPendingTimeSet() 3501 3502set ticket pending time: 3503 3504 my $Success = $TicketObject->TicketPendingTimeSet( 3505 Year => 2003, 3506 Month => 08, 3507 Day => 14, 3508 Hour => 22, 3509 Minute => 05, 3510 TicketID => 123, 3511 UserID => 23, 3512 ); 3513 3514or use a time stamp: 3515 3516 my $Success = $TicketObject->TicketPendingTimeSet( 3517 String => '2003-08-14 22:05:00', 3518 TicketID => 123, 3519 UserID => 23, 3520 ); 3521 3522or use a diff (set pending time to "now" + diff minutes) 3523 3524 my $Success = $TicketObject->TicketPendingTimeSet( 3525 Diff => ( 7 * 24 * 60 ), # minutes (here: 10080 minutes - 7 days) 3526 TicketID => 123, 3527 UserID => 23, 3528 ); 3529 3530If you want to set the pending time to null, just supply zeros: 3531 3532 my $Success = $TicketObject->TicketPendingTimeSet( 3533 Year => 0000, 3534 Month => 00, 3535 Day => 00, 3536 Hour => 00, 3537 Minute => 00, 3538 TicketID => 123, 3539 UserID => 23, 3540 ); 3541 3542or use a time stamp: 3543 3544 my $Success = $TicketObject->TicketPendingTimeSet( 3545 String => '0000-00-00 00:00:00', 3546 TicketID => 123, 3547 UserID => 23, 3548 ); 3549 3550Events: 3551 TicketPendingTimeUpdate 3552 3553=cut 3554 3555sub TicketPendingTimeSet { 3556 my ( $Self, %Param ) = @_; 3557 3558 # check needed stuff 3559 if ( !$Param{String} && !$Param{Diff} ) { 3560 for my $Needed (qw(Year Month Day Hour Minute TicketID UserID)) { 3561 if ( !defined $Param{$Needed} ) { 3562 $Kernel::OM->Get('Kernel::System::Log')->Log( 3563 Priority => 'error', 3564 Message => "Need $Needed!" 3565 ); 3566 return; 3567 } 3568 } 3569 } 3570 elsif ( 3571 !$Param{String} && 3572 !( $Param{Year} && $Param{Month} && $Param{Day} && $Param{Hour} && $Param{Minute} ) 3573 ) 3574 { 3575 for my $Needed (qw(Diff TicketID UserID)) { 3576 if ( !defined $Param{$Needed} ) { 3577 $Kernel::OM->Get('Kernel::System::Log')->Log( 3578 Priority => 'error', 3579 Message => "Need $Needed!" 3580 ); 3581 return; 3582 } 3583 } 3584 } 3585 else { 3586 for my $Needed (qw(String TicketID UserID)) { 3587 if ( !defined $Param{$Needed} ) { 3588 $Kernel::OM->Get('Kernel::System::Log')->Log( 3589 Priority => 'error', 3590 Message => "Need $Needed!" 3591 ); 3592 return; 3593 } 3594 } 3595 } 3596 3597 # check if we need to null the PendingTime 3598 my $PendingTimeNull; 3599 if ( $Param{String} && $Param{String} eq '0000-00-00 00:00:00' ) { 3600 $PendingTimeNull = 1; 3601 $Param{Sec} = 0; 3602 $Param{Minute} = 0; 3603 $Param{Hour} = 0; 3604 $Param{Day} = 0; 3605 $Param{Month} = 0; 3606 $Param{Year} = 0; 3607 } 3608 elsif ( 3609 !$Param{String} 3610 && !$Param{Diff} 3611 && $Param{Minute} == 0 3612 && $Param{Hour} == 0 && $Param{Day} == 0 3613 && $Param{Month} == 0 3614 && $Param{Year} == 0 3615 ) 3616 { 3617 $PendingTimeNull = 1; 3618 } 3619 3620 # get system time from string/params 3621 my $Time = 0; 3622 if ( !$PendingTimeNull ) { 3623 3624 if ( $Param{String} ) { 3625 3626 my $DateTimeObject = $Kernel::OM->Create( 3627 'Kernel::System::DateTime', 3628 ObjectParams => { 3629 String => $Param{String} 3630 } 3631 ); 3632 return if ( !$DateTimeObject ); 3633 3634 $Time = $DateTimeObject->ToEpoch(); 3635 3636 my $DateTimeValues = $DateTimeObject->Get(); 3637 $Param{Sec} = $DateTimeValues->{Second}; 3638 $Param{Minute} = $DateTimeValues->{Minute}; 3639 $Param{Hour} = $DateTimeValues->{Hour}; 3640 $Param{Day} = $DateTimeValues->{Day}; 3641 $Param{Month} = $DateTimeValues->{Month}; 3642 $Param{Year} = $DateTimeValues->{Year}; 3643 } 3644 elsif ( $Param{Diff} ) { 3645 3646 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 3647 $DateTimeObject->Add( Minutes => $Param{Diff} ); 3648 3649 $Time = $DateTimeObject->ToEpoch(); 3650 3651 my $DateTimeValues = $DateTimeObject->Get(); 3652 $Param{Sec} = $DateTimeValues->{Second}; 3653 $Param{Minute} = $DateTimeValues->{Minute}; 3654 $Param{Hour} = $DateTimeValues->{Hour}; 3655 $Param{Day} = $DateTimeValues->{Day}; 3656 $Param{Month} = $DateTimeValues->{Month}; 3657 $Param{Year} = $DateTimeValues->{Year}; 3658 } 3659 else { 3660 # create datetime object 3661 my $DateTimeObject = $Kernel::OM->Create( 3662 'Kernel::System::DateTime', 3663 ObjectParams => { 3664 String => "$Param{Year}-$Param{Month}-$Param{Day} $Param{Hour}:$Param{Minute}:00", 3665 } 3666 ); 3667 return if !$DateTimeObject; 3668 $Time = $DateTimeObject->ToEpoch(); 3669 } 3670 3671 # return if no convert is possible 3672 return if !$Time; 3673 } 3674 3675 # db update 3676 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 3677 SQL => 'UPDATE ticket SET until_time = ?, change_time = current_timestamp, change_by = ?' 3678 . ' WHERE id = ?', 3679 Bind => [ \$Time, \$Param{UserID}, \$Param{TicketID} ], 3680 ); 3681 3682 # clear ticket cache 3683 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 3684 3685 # history insert 3686 $Self->HistoryAdd( 3687 TicketID => $Param{TicketID}, 3688 HistoryType => 'SetPendingTime', 3689 Name => '%%' 3690 . sprintf( "%02d", $Param{Year} ) . '-' 3691 . sprintf( "%02d", $Param{Month} ) . '-' 3692 . sprintf( "%02d", $Param{Day} ) . ' ' 3693 . sprintf( "%02d", $Param{Hour} ) . ':' 3694 . sprintf( "%02d", $Param{Minute} ) . '', 3695 CreateUserID => $Param{UserID}, 3696 ); 3697 3698 # trigger event 3699 $Self->EventHandler( 3700 Event => 'TicketPendingTimeUpdate', 3701 Data => { 3702 TicketID => $Param{TicketID}, 3703 }, 3704 UserID => $Param{UserID}, 3705 ); 3706 3707 return 1; 3708} 3709 3710=head2 TicketLockGet() 3711 3712check if a ticket is locked or not 3713 3714 if ($TicketObject->TicketLockGet(TicketID => 123)) { 3715 print "Ticket is locked!\n"; 3716 } 3717 else { 3718 print "Ticket is not locked!\n"; 3719 } 3720 3721=cut 3722 3723sub TicketLockGet { 3724 my ( $Self, %Param ) = @_; 3725 3726 # check needed stuff 3727 if ( !$Param{TicketID} ) { 3728 $Kernel::OM->Get('Kernel::System::Log')->Log( 3729 Priority => 'error', 3730 Message => 'Need TicketID!' 3731 ); 3732 return; 3733 } 3734 3735 my %Ticket = $Self->TicketGet( 3736 %Param, 3737 DynamicFields => 0, 3738 ); 3739 3740 # check lock state 3741 return 1 if lc $Ticket{Lock} eq 'lock'; 3742 3743 return; 3744} 3745 3746=head2 TicketLockSet() 3747 3748to lock or unlock a ticket 3749 3750 my $Success = $TicketObject->TicketLockSet( 3751 Lock => 'lock', 3752 TicketID => 123, 3753 UserID => 123, 3754 ); 3755 3756 my $Success = $TicketObject->TicketLockSet( 3757 LockID => 1, 3758 TicketID => 123, 3759 UserID => 123, 3760 ); 3761 3762Optional attribute: 3763SendNoNotification, disable or enable agent and customer notification for this 3764action. Otherwise a notification will be sent to agent and customer. 3765 3766For example: 3767 3768 SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) 3769 3770Events: 3771 TicketLockUpdate 3772 3773=cut 3774 3775sub TicketLockSet { 3776 my ( $Self, %Param ) = @_; 3777 3778 # lookup! 3779 if ( !$Param{LockID} && $Param{Lock} ) { 3780 3781 $Param{LockID} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup( 3782 Lock => $Param{Lock}, 3783 ); 3784 } 3785 if ( $Param{LockID} && !$Param{Lock} ) { 3786 3787 $Param{Lock} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup( 3788 LockID => $Param{LockID}, 3789 ); 3790 } 3791 3792 # check needed stuff 3793 for my $Needed (qw(TicketID UserID LockID Lock)) { 3794 if ( !$Param{$Needed} ) { 3795 $Kernel::OM->Get('Kernel::System::Log')->Log( 3796 Priority => 'error', 3797 Message => "Need $Needed!" 3798 ); 3799 return; 3800 } 3801 } 3802 if ( !$Param{Lock} && !$Param{LockID} ) { 3803 $Kernel::OM->Get('Kernel::System::Log')->Log( 3804 Priority => 'error', 3805 Message => 'Need LockID or Lock!' 3806 ); 3807 return; 3808 } 3809 3810 # check if update is needed 3811 my %Ticket = $Self->TicketGet( 3812 %Param, 3813 DynamicFields => 0, 3814 ); 3815 return 1 if $Ticket{Lock} eq $Param{Lock}; 3816 3817 # db update 3818 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 3819 SQL => 'UPDATE ticket SET ticket_lock_id = ?, ' 3820 . ' change_time = current_timestamp, change_by = ? WHERE id = ?', 3821 Bind => [ \$Param{LockID}, \$Param{UserID}, \$Param{TicketID} ], 3822 ); 3823 3824 # clear ticket cache 3825 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 3826 3827 # add history 3828 my $HistoryType = ''; 3829 if ( lc $Param{Lock} eq 'unlock' ) { 3830 $HistoryType = 'Unlock'; 3831 } 3832 elsif ( lc $Param{Lock} eq 'lock' ) { 3833 $HistoryType = 'Lock'; 3834 } 3835 else { 3836 $HistoryType = 'Misc'; 3837 } 3838 if ($HistoryType) { 3839 $Self->HistoryAdd( 3840 TicketID => $Param{TicketID}, 3841 CreateUserID => $Param{UserID}, 3842 HistoryType => $HistoryType, 3843 Name => "\%\%$Param{Lock}", 3844 ); 3845 } 3846 3847 # set unlock time it event is 'lock' 3848 if ( $Param{Lock} eq 'lock' ) { 3849 3850 # create datetime object 3851 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 3852 3853 $Self->TicketUnlockTimeoutUpdate( 3854 UnlockTimeout => $DateTimeObject->ToEpoch(), 3855 TicketID => $Param{TicketID}, 3856 UserID => $Param{UserID}, 3857 ); 3858 } 3859 3860 # send unlock notify 3861 if ( lc $Param{Lock} eq 'unlock' ) { 3862 3863 my $Notification = defined $Param{Notification} ? $Param{Notification} : 1; 3864 if ( !$Param{SendNoNotification} && $Notification ) 3865 { 3866 my @SkipRecipients; 3867 if ( $Ticket{OwnerID} eq $Param{UserID} ) { 3868 @SkipRecipients = [ $Param{UserID} ]; 3869 } 3870 3871 # trigger notification event 3872 $Self->EventHandler( 3873 Event => 'NotificationLockTimeout', 3874 SkipRecipients => \@SkipRecipients, 3875 Data => { 3876 TicketID => $Param{TicketID}, 3877 CustomerMessageParams => {}, 3878 }, 3879 UserID => $Param{UserID}, 3880 ); 3881 } 3882 } 3883 3884 # trigger event 3885 $Self->EventHandler( 3886 Event => 'TicketLockUpdate', 3887 Data => { 3888 TicketID => $Param{TicketID}, 3889 }, 3890 UserID => $Param{UserID}, 3891 ); 3892 3893 return 1; 3894} 3895 3896=head2 TicketArchiveFlagSet() 3897 3898to set the ticket archive flag 3899 3900 my $Success = $TicketObject->TicketArchiveFlagSet( 3901 ArchiveFlag => 'y', # (y|n) 3902 TicketID => 123, 3903 UserID => 123, 3904 ); 3905 3906Events: 3907 TicketArchiveFlagUpdate 3908 3909=cut 3910 3911sub TicketArchiveFlagSet { 3912 my ( $Self, %Param ) = @_; 3913 3914 # check needed stuff 3915 for my $Needed (qw(TicketID UserID ArchiveFlag)) { 3916 if ( !$Param{$Needed} ) { 3917 $Kernel::OM->Get('Kernel::System::Log')->Log( 3918 Priority => 'error', 3919 Message => "Need $Needed!", 3920 ); 3921 return; 3922 } 3923 } 3924 3925 # get config object 3926 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 3927 3928 # return if feature is not enabled 3929 return if !$ConfigObject->Get('Ticket::ArchiveSystem'); 3930 3931 # check given archive flag 3932 if ( $Param{ArchiveFlag} ne 'y' && $Param{ArchiveFlag} ne 'n' ) { 3933 $Kernel::OM->Get('Kernel::System::Log')->Log( 3934 Priority => 'error', 3935 Message => "ArchiveFlag is invalid '$Param{ArchiveFlag}'!", 3936 ); 3937 return; 3938 } 3939 3940 # check if update is needed 3941 my %Ticket = $Self->TicketGet( 3942 %Param, 3943 DynamicFields => 0, 3944 ); 3945 3946 # return if no update is needed 3947 return 1 if $Ticket{ArchiveFlag} && $Ticket{ArchiveFlag} eq $Param{ArchiveFlag}; 3948 3949 # translate archive flag 3950 my $ArchiveFlag = $Param{ArchiveFlag} eq 'y' ? 1 : 0; 3951 3952 # set new archive flag 3953 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 3954 SQL => ' 3955 UPDATE ticket 3956 SET archive_flag = ?, change_time = current_timestamp, change_by = ? 3957 WHERE id = ?', 3958 Bind => [ \$ArchiveFlag, \$Param{UserID}, \$Param{TicketID} ], 3959 ); 3960 3961 # clear ticket cache 3962 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 3963 3964 # Remove seen flags from ticket and article and ticket watcher data if configured 3965 # and if the ticket flag was just set. 3966 if ($ArchiveFlag) { 3967 3968 if ( $ConfigObject->Get('Ticket::ArchiveSystem::RemoveSeenFlags') ) { 3969 $Self->TicketFlagDelete( 3970 TicketID => $Param{TicketID}, 3971 Key => 'Seen', 3972 AllUsers => 1, 3973 ); 3974 3975 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 3976 3977 my @Articles = $ArticleObject->ArticleList( TicketID => $Param{TicketID} ); 3978 for my $Article (@Articles) { 3979 $ArticleObject->ArticleFlagDelete( 3980 TicketID => $Param{TicketID}, 3981 ArticleID => $Article->{ArticleID}, 3982 Key => 'Seen', 3983 AllUsers => 1, 3984 ); 3985 } 3986 } 3987 3988 if ( 3989 $ConfigObject->Get('Ticket::ArchiveSystem::RemoveTicketWatchers') 3990 && $ConfigObject->Get('Ticket::Watcher') 3991 ) 3992 { 3993 $Self->TicketWatchUnsubscribe( 3994 TicketID => $Param{TicketID}, 3995 AllUsers => 1, 3996 UserID => $Param{UserID}, 3997 ); 3998 } 3999 } 4000 4001 # add history 4002 $Self->HistoryAdd( 4003 TicketID => $Param{TicketID}, 4004 CreateUserID => $Param{UserID}, 4005 HistoryType => 'ArchiveFlagUpdate', 4006 Name => "\%\%$Param{ArchiveFlag}", 4007 ); 4008 4009 # trigger event 4010 $Self->EventHandler( 4011 Event => 'TicketArchiveFlagUpdate', 4012 Data => { 4013 TicketID => $Param{TicketID}, 4014 }, 4015 UserID => $Param{UserID}, 4016 ); 4017 4018 return 1; 4019} 4020 4021=head2 TicketArchiveFlagGet() 4022 4023check if a ticket is archived or not 4024 4025 if ( $TicketObject->TicketArchiveFlagGet( TicketID => 123 ) ) { 4026 print "Ticket is archived!\n"; 4027 } 4028 else { 4029 print "Ticket is not archived!\n"; 4030 } 4031 4032=cut 4033 4034sub TicketArchiveFlagGet { 4035 my ( $Self, %Param ) = @_; 4036 4037 # check needed stuff 4038 if ( !$Param{TicketID} ) { 4039 $Kernel::OM->Get('Kernel::System::Log')->Log( 4040 Priority => 'error', 4041 Message => 'Need TicketID!' 4042 ); 4043 return; 4044 } 4045 4046 my %Ticket = $Self->TicketGet( 4047 %Param, 4048 DynamicFields => 0, 4049 ); 4050 4051 # check archive state 4052 return 1 if lc $Ticket{ArchiveFlag} eq 'y'; 4053 4054 return; 4055} 4056 4057=head2 TicketStateSet() 4058 4059to set a ticket state 4060 4061 my $Success = $TicketObject->TicketStateSet( 4062 State => 'open', 4063 TicketID => 123, 4064 ArticleID => 123, #optional, for history 4065 UserID => 123, 4066 ); 4067 4068 my $Success = $TicketObject->TicketStateSet( 4069 StateID => 3, 4070 TicketID => 123, 4071 UserID => 123, 4072 ); 4073 4074Optional attribute: 4075SendNoNotification, disable or enable agent and customer notification for this 4076action. Otherwise a notification will be sent to agent and customer. 4077 4078For example: 4079 4080 SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) 4081 4082Events: 4083 TicketStateUpdate 4084 4085=cut 4086 4087sub TicketStateSet { 4088 my ( $Self, %Param ) = @_; 4089 4090 my %State; 4091 my $ArticleID = $Param{ArticleID} || ''; 4092 4093 # check needed stuff 4094 for my $Needed (qw(TicketID UserID)) { 4095 if ( !$Param{$Needed} ) { 4096 $Kernel::OM->Get('Kernel::System::Log')->Log( 4097 Priority => 'error', 4098 Message => "Need $Needed!" 4099 ); 4100 return; 4101 } 4102 } 4103 if ( !$Param{State} && !$Param{StateID} ) { 4104 $Kernel::OM->Get('Kernel::System::Log')->Log( 4105 Priority => 'error', 4106 Message => 'Need StateID or State!' 4107 ); 4108 return; 4109 } 4110 4111 # get state object 4112 my $StateObject = $Kernel::OM->Get('Kernel::System::State'); 4113 4114 # state id lookup 4115 if ( !$Param{StateID} ) { 4116 %State = $StateObject->StateGet( Name => $Param{State} ); 4117 } 4118 4119 # state lookup 4120 if ( !$Param{State} ) { 4121 %State = $StateObject->StateGet( ID => $Param{StateID} ); 4122 } 4123 if ( !%State ) { 4124 $Kernel::OM->Get('Kernel::System::Log')->Log( 4125 Priority => 'error', 4126 Message => 'Need StateID or State!' 4127 ); 4128 return; 4129 } 4130 4131 # check if update is needed 4132 my %Ticket = $Self->TicketGet( 4133 TicketID => $Param{TicketID}, 4134 DynamicFields => 0, 4135 ); 4136 if ( $State{Name} eq $Ticket{State} ) { 4137 4138 # update is not needed 4139 return 1; 4140 } 4141 4142 # permission check 4143 my %StateList = $Self->StateList(%Param); 4144 if ( !$StateList{ $State{ID} } ) { 4145 $Kernel::OM->Get('Kernel::System::Log')->Log( 4146 Priority => 'notice', 4147 Message => "Permission denied on TicketID: $Param{TicketID} / StateID: $State{ID}!", 4148 ); 4149 return; 4150 } 4151 4152 # db update 4153 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 4154 SQL => 'UPDATE ticket SET ticket_state_id = ?, ' 4155 . ' change_time = current_timestamp, change_by = ? WHERE id = ?', 4156 Bind => [ \$State{ID}, \$Param{UserID}, \$Param{TicketID} ], 4157 ); 4158 4159 # clear ticket cache 4160 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 4161 4162 # add history 4163 $Self->HistoryAdd( 4164 TicketID => $Param{TicketID}, 4165 StateID => $State{ID}, 4166 ArticleID => $ArticleID, 4167 QueueID => $Ticket{QueueID}, 4168 Name => "\%\%$Ticket{State}\%\%$State{Name}\%\%", 4169 HistoryType => 'StateUpdate', 4170 CreateUserID => $Param{UserID}, 4171 ); 4172 4173 # trigger event, OldTicketData is needed for escalation events 4174 $Self->EventHandler( 4175 Event => 'TicketStateUpdate', 4176 Data => { 4177 TicketID => $Param{TicketID}, 4178 OldTicketData => \%Ticket, 4179 }, 4180 UserID => $Param{UserID}, 4181 ); 4182 4183 return 1; 4184} 4185 4186=head2 TicketStateList() 4187 4188to get the state list for a ticket (depends on workflow, if configured) 4189 4190 my %States = $TicketObject->TicketStateList( 4191 TicketID => 123, 4192 UserID => 123, 4193 ); 4194 4195 my %States = $TicketObject->TicketStateList( 4196 TicketID => 123, 4197 CustomerUserID => 'customer_user_id_123', 4198 ); 4199 4200 my %States = $TicketObject->TicketStateList( 4201 QueueID => 123, 4202 UserID => 123, 4203 ); 4204 4205 my %States = $TicketObject->TicketStateList( 4206 TicketID => 123, 4207 Type => 'open', 4208 UserID => 123, 4209 ); 4210 4211Returns: 4212 4213 %States = ( 4214 1 => 'State A', 4215 2 => 'State B', 4216 3 => 'State C', 4217 ); 4218 4219=cut 4220 4221sub TicketStateList { 4222 my ( $Self, %Param ) = @_; 4223 4224 my %States; 4225 4226 # check needed stuff 4227 if ( !$Param{UserID} && !$Param{CustomerUserID} ) { 4228 $Kernel::OM->Get('Kernel::System::Log')->Log( 4229 Priority => 'error', 4230 Message => 'Need UserID or CustomerUserID!' 4231 ); 4232 return; 4233 } 4234 4235 # check needed stuff 4236 if ( !$Param{QueueID} && !$Param{TicketID} ) { 4237 $Kernel::OM->Get('Kernel::System::Log')->Log( 4238 Priority => 'error', 4239 Message => 'Need QueueID, TicketID!' 4240 ); 4241 return; 4242 } 4243 4244 # get config object 4245 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 4246 4247 # get state object 4248 my $StateObject = $Kernel::OM->Get('Kernel::System::State'); 4249 4250 # get states by type 4251 if ( $Param{Type} ) { 4252 %States = $StateObject->StateGetStatesByType( 4253 Type => $Param{Type}, 4254 Result => 'HASH', 4255 ); 4256 } 4257 elsif ( $Param{Action} ) { 4258 4259 if ( 4260 ref $ConfigObject->Get("Ticket::Frontend::$Param{Action}")->{StateType} ne 4261 'ARRAY' 4262 ) 4263 { 4264 $Kernel::OM->Get('Kernel::System::Log')->Log( 4265 Priority => 'error', 4266 Message => "Need config for Ticket::Frontend::$Param{Action}->StateType!" 4267 ); 4268 return; 4269 } 4270 4271 my @StateType = @{ $ConfigObject->Get("Ticket::Frontend::$Param{Action}")->{StateType} }; 4272 %States = $StateObject->StateGetStatesByType( 4273 StateType => \@StateType, 4274 Result => 'HASH', 4275 ); 4276 } 4277 4278 # get whole states list 4279 else { 4280 %States = $StateObject->StateList( 4281 UserID => $Param{UserID}, 4282 ); 4283 } 4284 4285 # workflow 4286 my $ACL = $Self->TicketAcl( 4287 %Param, 4288 ReturnType => 'Ticket', 4289 ReturnSubType => 'State', 4290 Data => \%States, 4291 ); 4292 4293 return $Self->TicketAclData() if $ACL; 4294 return %States; 4295} 4296 4297=head2 OwnerCheck() 4298 4299to get the ticket owner 4300 4301 my ($OwnerID, $Owner) = $TicketObject->OwnerCheck( 4302 TicketID => 123, 4303 ); 4304 4305or for access control 4306 4307 my $AccessOk = $TicketObject->OwnerCheck( 4308 TicketID => 123, 4309 OwnerID => 321, 4310 ); 4311 4312=cut 4313 4314sub OwnerCheck { 4315 my ( $Self, %Param ) = @_; 4316 4317 my $SQL = ''; 4318 4319 # check needed stuff 4320 if ( !$Param{TicketID} ) { 4321 $Kernel::OM->Get('Kernel::System::Log')->Log( 4322 Priority => 'error', 4323 Message => 'Need TicketID!' 4324 ); 4325 return; 4326 } 4327 4328 # get database object 4329 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 4330 4331 # db query 4332 if ( $Param{OwnerID} ) { 4333 4334 # create cache key 4335 my $CacheKey = $Param{TicketID} . '::' . $Param{OwnerID}; 4336 4337 # check cache 4338 if ( defined $Self->{OwnerCheck}->{$CacheKey} ) { 4339 return if !$Self->{OwnerCheck}->{$CacheKey}; 4340 return 1 if $Self->{OwnerCheck}->{$CacheKey}; 4341 } 4342 4343 # check if user has access 4344 return if !$DBObject->Prepare( 4345 SQL => 'SELECT user_id FROM ticket WHERE ' 4346 . ' id = ? AND (user_id = ? OR responsible_user_id = ?)', 4347 Bind => [ \$Param{TicketID}, \$Param{OwnerID}, \$Param{OwnerID}, ], 4348 ); 4349 my $Access = 0; 4350 while ( my @Row = $DBObject->FetchrowArray() ) { 4351 $Access = 1; 4352 } 4353 4354 # fill cache 4355 $Self->{OwnerCheck}->{$CacheKey} = $Access; 4356 return if !$Access; 4357 return 1 if $Access; 4358 } 4359 4360 # get config object 4361 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 4362 4363 # search for owner_id and owner 4364 return if !$DBObject->Prepare( 4365 SQL => "SELECT st.user_id, su.$ConfigObject->{DatabaseUserTableUser} " 4366 . " FROM ticket st, $ConfigObject->{DatabaseUserTable} su " 4367 . " WHERE st.id = ? AND " 4368 . " st.user_id = su.$ConfigObject->{DatabaseUserTableUserID}", 4369 Bind => [ \$Param{TicketID}, ], 4370 ); 4371 4372 while ( my @Row = $DBObject->FetchrowArray() ) { 4373 $Param{SearchUserID} = $Row[0]; 4374 $Param{SearchUser} = $Row[1]; 4375 } 4376 4377 # return if no owner as been found 4378 return if !$Param{SearchUserID}; 4379 4380 # return owner id and owner 4381 return $Param{SearchUserID}, $Param{SearchUser}; 4382} 4383 4384=head2 TicketOwnerSet() 4385 4386to set the ticket owner (notification to the new owner will be sent) 4387 4388by using user id 4389 4390 my $Success = $TicketObject->TicketOwnerSet( 4391 TicketID => 123, 4392 NewUserID => 555, 4393 UserID => 123, 4394 ); 4395 4396by using user login 4397 4398 my $Success = $TicketObject->TicketOwnerSet( 4399 TicketID => 123, 4400 NewUser => 'some-user-login', 4401 UserID => 123, 4402 ); 4403 4404Return: 4405 1 = owner has been set 4406 2 = this owner is already set, no update needed 4407 4408Optional attribute: 4409SendNoNotification, disable or enable agent and customer notification for this 4410action. Otherwise a notification will be sent to agent and customer. 4411 4412For example: 4413 4414 SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) 4415 4416Events: 4417 TicketOwnerUpdate 4418 4419=cut 4420 4421sub TicketOwnerSet { 4422 my ( $Self, %Param ) = @_; 4423 4424 # check needed stuff 4425 for my $Needed (qw(TicketID UserID)) { 4426 if ( !$Param{$Needed} ) { 4427 $Kernel::OM->Get('Kernel::System::Log')->Log( 4428 Priority => 'error', 4429 Message => "Need $Needed!" 4430 ); 4431 return; 4432 } 4433 } 4434 if ( !$Param{NewUserID} && !$Param{NewUser} ) { 4435 $Kernel::OM->Get('Kernel::System::Log')->Log( 4436 Priority => 'error', 4437 Message => 'Need NewUserID or NewUser!' 4438 ); 4439 return; 4440 } 4441 4442 # get user object 4443 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 4444 4445 # lookup if no NewUserID is given 4446 if ( !$Param{NewUserID} ) { 4447 $Param{NewUserID} = $UserObject->UserLookup( 4448 UserLogin => $Param{NewUser}, 4449 ); 4450 } 4451 4452 # lookup if no NewUser is given 4453 if ( !$Param{NewUser} ) { 4454 $Param{NewUser} = $UserObject->UserLookup( 4455 UserID => $Param{NewUserID}, 4456 ); 4457 } 4458 4459 # make sure the user exists 4460 if ( !$UserObject->UserLookup( UserID => $Param{NewUserID} ) ) { 4461 $Kernel::OM->Get('Kernel::System::Log')->Log( 4462 Priority => 'error', 4463 Message => "User does not exist.", 4464 ); 4465 return; 4466 } 4467 4468 # check if update is needed! 4469 my ( $OwnerID, $Owner ) = $Self->OwnerCheck( TicketID => $Param{TicketID} ); 4470 if ( $OwnerID eq $Param{NewUserID} ) { 4471 4472 # update is "not" needed! 4473 return 2; 4474 } 4475 4476 # db update 4477 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 4478 SQL => 'UPDATE ticket SET ' 4479 . ' user_id = ?, change_time = current_timestamp, change_by = ? WHERE id = ?', 4480 Bind => [ \$Param{NewUserID}, \$Param{UserID}, \$Param{TicketID} ], 4481 ); 4482 4483 # clear ticket cache 4484 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 4485 4486 # add history 4487 $Self->HistoryAdd( 4488 TicketID => $Param{TicketID}, 4489 CreateUserID => $Param{UserID}, 4490 HistoryType => 'OwnerUpdate', 4491 Name => "\%\%$Param{NewUser}\%\%$Param{NewUserID}", 4492 ); 4493 4494 # send agent notify 4495 if ( !$Param{SendNoNotification} ) { 4496 4497 my @SkipRecipients; 4498 if ( $Param{UserID} eq $Param{NewUserID} ) { 4499 @SkipRecipients = [ $Param{UserID} ]; 4500 } 4501 4502 # trigger notification event 4503 $Self->EventHandler( 4504 Event => 'NotificationOwnerUpdate', 4505 Data => { 4506 TicketID => $Param{TicketID}, 4507 SkipRecipients => \@SkipRecipients, 4508 CustomerMessageParams => { 4509 %Param, 4510 Body => $Param{Comment} || '', 4511 }, 4512 }, 4513 UserID => $Param{UserID}, 4514 ); 4515 } 4516 4517 # trigger event 4518 $Self->EventHandler( 4519 Event => 'TicketOwnerUpdate', 4520 Data => { 4521 TicketID => $Param{TicketID}, 4522 }, 4523 UserID => $Param{UserID}, 4524 ); 4525 4526 return 1; 4527} 4528 4529=head2 TicketOwnerList() 4530 4531returns the owner in the past as array with hash ref of the owner data 4532(name, email, ...) 4533 4534 my @Owner = $TicketObject->TicketOwnerList( 4535 TicketID => 123, 4536 ); 4537 4538Returns: 4539 4540 @Owner = ( 4541 { 4542 UserFirstname => 'SomeName', 4543 UserLastname => 'SomeName', 4544 UserEmail => 'some@example.com', 4545 # custom attributes 4546 }, 4547 { 4548 UserFirstname => 'SomeName', 4549 UserLastname => 'SomeName', 4550 UserEmail => 'some@example.com', 4551 # custom attributes 4552 }, 4553 ); 4554 4555=cut 4556 4557sub TicketOwnerList { 4558 my ( $Self, %Param ) = @_; 4559 4560 # check needed stuff 4561 if ( !$Param{TicketID} ) { 4562 $Kernel::OM->Get('Kernel::System::Log')->Log( 4563 Priority => 'error', 4564 Message => "Need TicketID!" 4565 ); 4566 return; 4567 } 4568 4569 # get database object 4570 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 4571 4572 # db query 4573 return if !$DBObject->Prepare( 4574 SQL => 'SELECT sh.owner_id FROM ticket_history sh, ticket_history_type ht WHERE ' 4575 . ' sh.ticket_id = ? AND ht.name IN (\'OwnerUpdate\', \'NewTicket\') AND ' 4576 . ' ht.id = sh.history_type_id ORDER BY sh.id', 4577 Bind => [ \$Param{TicketID} ], 4578 ); 4579 my @UserID; 4580 4581 USER: 4582 while ( my @Row = $DBObject->FetchrowArray() ) { 4583 next USER if !$Row[0]; 4584 next USER if $Row[0] eq 1; 4585 push @UserID, $Row[0]; 4586 } 4587 4588 # get user object 4589 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 4590 4591 my @UserInfo; 4592 USER: 4593 for my $UserID (@UserID) { 4594 4595 my %User = $UserObject->GetUserData( 4596 UserID => $UserID, 4597 Cache => 1, 4598 Valid => 1, 4599 ); 4600 4601 next USER if !%User; 4602 4603 push @UserInfo, \%User; 4604 } 4605 4606 return @UserInfo; 4607} 4608 4609=head2 TicketResponsibleSet() 4610 4611to set the ticket responsible (notification to the new responsible will be sent) 4612 4613by using user id 4614 4615 my $Success = $TicketObject->TicketResponsibleSet( 4616 TicketID => 123, 4617 NewUserID => 555, 4618 UserID => 213, 4619 ); 4620 4621by using user login 4622 4623 my $Success = $TicketObject->TicketResponsibleSet( 4624 TicketID => 123, 4625 NewUser => 'some-user-login', 4626 UserID => 213, 4627 ); 4628 4629Return: 4630 1 = responsible has been set 4631 2 = this responsible is already set, no update needed 4632 4633Optional attribute: 4634SendNoNotification, disable or enable agent and customer notification for this 4635action. Otherwise a notification will be sent to agent and customer. 4636 4637For example: 4638 4639 SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) 4640 4641Events: 4642 TicketResponsibleUpdate 4643 4644=cut 4645 4646sub TicketResponsibleSet { 4647 my ( $Self, %Param ) = @_; 4648 4649 # check needed stuff 4650 for my $Needed (qw(TicketID UserID)) { 4651 if ( !$Param{$Needed} ) { 4652 $Kernel::OM->Get('Kernel::System::Log')->Log( 4653 Priority => 'error', 4654 Message => "Need $Needed!" 4655 ); 4656 return; 4657 } 4658 } 4659 if ( !$Param{NewUserID} && !$Param{NewUser} ) { 4660 $Kernel::OM->Get('Kernel::System::Log')->Log( 4661 Priority => 'error', 4662 Message => 'Need NewUserID or NewUser!' 4663 ); 4664 return; 4665 } 4666 4667 # get user object 4668 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 4669 4670 # lookup if no NewUserID is given 4671 if ( !$Param{NewUserID} ) { 4672 $Param{NewUserID} = $UserObject->UserLookup( UserLogin => $Param{NewUser} ); 4673 } 4674 4675 # lookup if no NewUser is given 4676 if ( !$Param{NewUser} ) { 4677 $Param{NewUser} = $UserObject->UserLookup( UserID => $Param{NewUserID} ); 4678 } 4679 4680 # make sure the user exists 4681 if ( !$UserObject->UserLookup( UserID => $Param{NewUserID} ) ) { 4682 $Kernel::OM->Get('Kernel::System::Log')->Log( 4683 Priority => 'error', 4684 Message => "User does not exist.", 4685 ); 4686 return; 4687 } 4688 4689 # check if update is needed! 4690 my %Ticket = $Self->TicketGet( 4691 TicketID => $Param{TicketID}, 4692 UserID => $Param{NewUserID}, 4693 DynamicFields => 0, 4694 ); 4695 if ( $Ticket{ResponsibleID} eq $Param{NewUserID} ) { 4696 4697 # update is "not" needed! 4698 return 2; 4699 } 4700 4701 # db update 4702 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 4703 SQL => 'UPDATE ticket SET responsible_user_id = ?, ' 4704 . ' change_time = current_timestamp, change_by = ? ' 4705 . ' WHERE id = ?', 4706 Bind => [ \$Param{NewUserID}, \$Param{UserID}, \$Param{TicketID} ], 4707 ); 4708 4709 # clear ticket cache 4710 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 4711 4712 # add history 4713 $Self->HistoryAdd( 4714 TicketID => $Param{TicketID}, 4715 CreateUserID => $Param{UserID}, 4716 HistoryType => 'ResponsibleUpdate', 4717 Name => "\%\%$Param{NewUser}\%\%$Param{NewUserID}", 4718 ); 4719 4720 # send agent notify 4721 if ( !$Param{SendNoNotification} ) { 4722 4723 my @SkipRecipients; 4724 if ( $Param{UserID} eq $Param{NewUserID} ) { 4725 @SkipRecipients = [ $Param{UserID} ]; 4726 } 4727 4728 # trigger notification event 4729 $Self->EventHandler( 4730 Event => 'NotificationResponsibleUpdate', 4731 Data => { 4732 TicketID => $Param{TicketID}, 4733 SkipRecipients => \@SkipRecipients, 4734 CustomerMessageParams => \%Param, 4735 }, 4736 UserID => $Param{UserID}, 4737 ); 4738 } 4739 4740 # trigger event 4741 $Self->EventHandler( 4742 Event => 'TicketResponsibleUpdate', 4743 Data => { 4744 TicketID => $Param{TicketID}, 4745 }, 4746 UserID => $Param{UserID}, 4747 ); 4748 4749 return 1; 4750} 4751 4752=head2 TicketResponsibleList() 4753 4754returns the responsible in the past as array with hash ref of the owner data 4755(name, email, ...) 4756 4757 my @Responsible = $TicketObject->TicketResponsibleList( 4758 TicketID => 123, 4759 ); 4760 4761Returns: 4762 4763 @Responsible = ( 4764 { 4765 UserFirstname => 'SomeName', 4766 UserLastname => 'SomeName', 4767 UserEmail => 'some@example.com', 4768 # custom attributes 4769 }, 4770 { 4771 UserFirstname => 'SomeName', 4772 UserLastname => 'SomeName', 4773 UserEmail => 'some@example.com', 4774 # custom attributes 4775 }, 4776 ); 4777 4778=cut 4779 4780sub TicketResponsibleList { 4781 my ( $Self, %Param ) = @_; 4782 4783 # check needed stuff 4784 if ( !$Param{TicketID} ) { 4785 $Kernel::OM->Get('Kernel::System::Log')->Log( 4786 Priority => 'error', 4787 Message => "Need TicketID!" 4788 ); 4789 return; 4790 } 4791 4792 # get database object 4793 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 4794 4795 # db query 4796 my @User; 4797 my $LastResponsible = 1; 4798 return if !$DBObject->Prepare( 4799 SQL => 'SELECT sh.name, ht.name, sh.create_by FROM ' 4800 . ' ticket_history sh, ticket_history_type ht WHERE ' 4801 . ' sh.ticket_id = ? AND ' 4802 . ' ht.name IN (\'ResponsibleUpdate\', \'NewTicket\') AND ' 4803 . ' ht.id = sh.history_type_id ORDER BY sh.id', 4804 Bind => [ \$Param{TicketID} ], 4805 ); 4806 4807 while ( my @Row = $DBObject->FetchrowArray() ) { 4808 4809 # store result 4810 if ( $Row[1] eq 'NewTicket' && $Row[2] ne '1' && $LastResponsible ne $Row[2] ) { 4811 $LastResponsible = $Row[2]; 4812 push @User, $Row[2]; 4813 } 4814 elsif ( $Row[1] eq 'ResponsibleUpdate' ) { 4815 if ( 4816 $Row[0] =~ /^New Responsible is '(.+?)' \(ID=(.+?)\)/ 4817 || $Row[0] =~ /^\%\%(.+?)\%\%(.+?)$/ 4818 ) 4819 { 4820 $LastResponsible = $2; 4821 push @User, $2; 4822 } 4823 } 4824 } 4825 4826 # get user object 4827 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 4828 4829 my @UserInfo; 4830 for my $SingleUser (@User) { 4831 4832 my %User = $UserObject->GetUserData( 4833 UserID => $SingleUser, 4834 Cache => 1 4835 ); 4836 push @UserInfo, \%User; 4837 } 4838 4839 return @UserInfo; 4840} 4841 4842=head2 TicketInvolvedAgentsList() 4843 4844returns an array with hash ref of agents which have been involved with a ticket. 4845It is guaranteed that no agent is returned twice. 4846 4847 my @InvolvedAgents = $TicketObject->TicketInvolvedAgentsList( 4848 TicketID => 123, 4849 ); 4850 4851Returns: 4852 4853 @InvolvedAgents = ( 4854 { 4855 UserFirstname => 'SomeName', 4856 UserLastname => 'SomeName', 4857 UserEmail => 'some@example.com', 4858 # custom attributes 4859 }, 4860 { 4861 UserFirstname => 'AnotherName', 4862 UserLastname => 'AnotherName', 4863 UserEmail => 'another@example.com', 4864 # custom attributes 4865 }, 4866 ); 4867 4868=cut 4869 4870sub TicketInvolvedAgentsList { 4871 my ( $Self, %Param ) = @_; 4872 4873 # check needed stuff 4874 if ( !$Param{TicketID} ) { 4875 $Kernel::OM->Get('Kernel::System::Log')->Log( 4876 Priority => 'error', 4877 Message => 'Need TicketID!' 4878 ); 4879 return; 4880 } 4881 4882 # get database object 4883 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 4884 4885 # db query, only entries with a known history_id are retrieved 4886 my @User; 4887 my %UsedOwner; 4888 return if !$DBObject->Prepare( 4889 SQL => '' 4890 . 'SELECT sh.create_by' 4891 . ' FROM ticket_history sh, ticket_history_type ht' 4892 . ' WHERE sh.ticket_id = ?' 4893 . ' AND ht.id = sh.history_type_id' 4894 . ' ORDER BY sh.id', 4895 Bind => [ \$Param{TicketID} ], 4896 ); 4897 4898 while ( my @Row = $DBObject->FetchrowArray() ) { 4899 4900 # store result, skip the 4901 if ( $Row[0] ne 1 && !$UsedOwner{ $Row[0] } ) { 4902 $UsedOwner{ $Row[0] } = $Row[0]; 4903 push @User, $Row[0]; 4904 } 4905 } 4906 4907 # get user object 4908 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 4909 4910 # collect agent information 4911 my @UserInfo; 4912 USER: 4913 for my $SingleUser (@User) { 4914 4915 my %User = $UserObject->GetUserData( 4916 UserID => $SingleUser, 4917 Valid => 1, 4918 Cache => 1, 4919 ); 4920 4921 next USER if !%User; 4922 4923 push @UserInfo, \%User; 4924 } 4925 4926 return @UserInfo; 4927} 4928 4929=head2 TicketPrioritySet() 4930 4931to set the ticket priority 4932 4933 my $Success = $TicketObject->TicketPrioritySet( 4934 TicketID => 123, 4935 Priority => 'low', 4936 UserID => 213, 4937 ); 4938 4939 my $Success = $TicketObject->TicketPrioritySet( 4940 TicketID => 123, 4941 PriorityID => 2, 4942 UserID => 213, 4943 ); 4944 4945Events: 4946 TicketPriorityUpdate 4947 4948=cut 4949 4950sub TicketPrioritySet { 4951 my ( $Self, %Param ) = @_; 4952 4953 # get priority object 4954 my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority'); 4955 4956 # lookup! 4957 if ( !$Param{PriorityID} && $Param{Priority} ) { 4958 $Param{PriorityID} = $PriorityObject->PriorityLookup( 4959 Priority => $Param{Priority}, 4960 ); 4961 } 4962 if ( $Param{PriorityID} && !$Param{Priority} ) { 4963 $Param{Priority} = $PriorityObject->PriorityLookup( 4964 PriorityID => $Param{PriorityID}, 4965 ); 4966 } 4967 4968 # check needed stuff 4969 for my $Needed (qw(TicketID UserID PriorityID Priority)) { 4970 if ( !$Param{$Needed} ) { 4971 $Kernel::OM->Get('Kernel::System::Log')->Log( 4972 Priority => 'error', 4973 Message => "Need $Needed!" 4974 ); 4975 return; 4976 } 4977 } 4978 my %Ticket = $Self->TicketGet( 4979 %Param, 4980 DynamicFields => 0, 4981 ); 4982 4983 # check if update is needed 4984 if ( $Ticket{Priority} eq $Param{Priority} ) { 4985 4986 # update not needed 4987 return 1; 4988 } 4989 4990 # permission check 4991 my %PriorityList = $Self->PriorityList(%Param); 4992 if ( !$PriorityList{ $Param{PriorityID} } ) { 4993 $Kernel::OM->Get('Kernel::System::Log')->Log( 4994 Priority => 'notice', 4995 Message => "Permission denied on TicketID: $Param{TicketID}!", 4996 ); 4997 return; 4998 } 4999 5000 # db update 5001 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 5002 SQL => 'UPDATE ticket SET ticket_priority_id = ?, ' 5003 . ' change_time = current_timestamp, change_by = ?' 5004 . ' WHERE id = ?', 5005 Bind => [ \$Param{PriorityID}, \$Param{UserID}, \$Param{TicketID} ], 5006 ); 5007 5008 # clear ticket cache 5009 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 5010 5011 # add history 5012 $Self->HistoryAdd( 5013 TicketID => $Param{TicketID}, 5014 QueueID => $Ticket{QueueID}, 5015 CreateUserID => $Param{UserID}, 5016 HistoryType => 'PriorityUpdate', 5017 Name => "\%\%$Ticket{Priority}\%\%$Ticket{PriorityID}" 5018 . "\%\%$Param{Priority}\%\%$Param{PriorityID}", 5019 ); 5020 5021 # trigger event 5022 $Self->EventHandler( 5023 Event => 'TicketPriorityUpdate', 5024 Data => { 5025 TicketID => $Param{TicketID}, 5026 }, 5027 UserID => $Param{UserID}, 5028 ); 5029 5030 return 1; 5031} 5032 5033=head2 TicketPriorityList() 5034 5035to get the priority list for a ticket (depends on workflow, if configured) 5036 5037 my %Priorities = $TicketObject->TicketPriorityList( 5038 TicketID => 123, 5039 UserID => 123, 5040 ); 5041 5042 my %Priorities = $TicketObject->TicketPriorityList( 5043 TicketID => 123, 5044 CustomerUserID => 'customer_user_id_123', 5045 ); 5046 5047 my %Priorities = $TicketObject->TicketPriorityList( 5048 QueueID => 123, 5049 UserID => 123, 5050 ); 5051 5052Returns: 5053 5054 %Priorities = ( 5055 1 => 'Priority A', 5056 2 => 'Priority B', 5057 3 => 'Priority C', 5058 ); 5059 5060=cut 5061 5062sub TicketPriorityList { 5063 my ( $Self, %Param ) = @_; 5064 5065 # check needed stuff 5066 if ( !$Param{UserID} && !$Param{CustomerUserID} ) { 5067 $Kernel::OM->Get('Kernel::System::Log')->Log( 5068 Priority => 'error', 5069 Message => 'Need UserID or CustomerUserID!' 5070 ); 5071 return; 5072 } 5073 5074 my %Data = $Kernel::OM->Get('Kernel::System::Priority')->PriorityList(%Param); 5075 5076 # workflow 5077 my $ACL = $Self->TicketAcl( 5078 %Param, 5079 ReturnType => 'Ticket', 5080 ReturnSubType => 'Priority', 5081 Data => \%Data, 5082 ); 5083 5084 return $Self->TicketAclData() if $ACL; 5085 return %Data; 5086} 5087 5088=head2 HistoryTicketStatusGet() 5089 5090get a hash with ticket id as key and a hash ref (result of HistoryTicketGet) 5091of all affected tickets in this time area. 5092 5093 my %Tickets = $TicketObject->HistoryTicketStatusGet( 5094 StartDay => 12, 5095 StartMonth => 1, 5096 StartYear => 2006, 5097 StopDay => 18, 5098 StopMonth => 1, 5099 StopYear => 2006, 5100 Force => 0, 5101 ); 5102 5103=cut 5104 5105sub HistoryTicketStatusGet { 5106 my ( $Self, %Param ) = @_; 5107 5108 # check needed stuff 5109 for my $Needed (qw(StopYear StopMonth StopDay StartYear StartMonth StartDay)) { 5110 if ( !$Param{$Needed} ) { 5111 $Kernel::OM->Get('Kernel::System::Log')->Log( 5112 Priority => 'error', 5113 Message => "Need $Needed!" 5114 ); 5115 return; 5116 } 5117 } 5118 5119 # format month and day params 5120 for my $DateParameter (qw(StopMonth StopDay StartMonth StartDay)) { 5121 $Param{$DateParameter} = sprintf( "%02d", $Param{$DateParameter} ); 5122 } 5123 5124 my $SQLExt = ''; 5125 for my $HistoryTypeData ( 5126 qw(NewTicket FollowUp OwnerUpdate PriorityUpdate CustomerUpdate StateUpdate 5127 PhoneCallCustomer Forward Bounce SendAnswer EmailCustomer 5128 PhoneCallAgent WebRequestCustomer TicketDynamicFieldUpdate) 5129 ) 5130 { 5131 my $ID = $Self->HistoryTypeLookup( Type => $HistoryTypeData ); 5132 if ( !$SQLExt ) { 5133 $SQLExt = "AND history_type_id IN ($ID"; 5134 } 5135 else { 5136 $SQLExt .= ",$ID"; 5137 } 5138 } 5139 5140 if ($SQLExt) { 5141 $SQLExt .= ')'; 5142 } 5143 5144 # assemble stop date/time string for database comparison 5145 my $StopDateTimeObject = $Kernel::OM->Create( 5146 'Kernel::System::DateTime', 5147 ObjectParams => { 5148 String => "$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} 00:00:00", 5149 } 5150 ); 5151 $StopDateTimeObject->Add( Hours => 24 ); 5152 my $StopDateTimeString = $StopDateTimeObject->Format( Format => '%Y-%m-%d 00:00:00' ); 5153 5154 # get database object 5155 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 5156 5157 return if !$DBObject->Prepare( 5158 SQL => " 5159 SELECT DISTINCT(th.ticket_id), th.create_time 5160 FROM ticket_history th 5161 WHERE th.create_time <= '$StopDateTimeString' 5162 AND th.create_time >= '$Param{StartYear}-$Param{StartMonth}-$Param{StartDay} 00:00:01' 5163 $SQLExt 5164 ORDER BY th.create_time DESC", 5165 Limit => 150000, 5166 ); 5167 5168 my %Ticket; 5169 while ( my @Row = $DBObject->FetchrowArray() ) { 5170 $Ticket{ $Row[0] } = 1; 5171 } 5172 5173 for my $TicketID ( sort keys %Ticket ) { 5174 5175 my %TicketData = $Self->HistoryTicketGet( 5176 TicketID => $TicketID, 5177 StopYear => $Param{StopYear}, 5178 StopMonth => $Param{StopMonth}, 5179 StopDay => $Param{StopDay}, 5180 Force => $Param{Force} || 0, 5181 ); 5182 5183 if (%TicketData) { 5184 $Ticket{$TicketID} = \%TicketData; 5185 } 5186 else { 5187 $Ticket{$TicketID} = {}; 5188 } 5189 } 5190 5191 return %Ticket; 5192} 5193 5194=head2 HistoryTicketGet() 5195 5196returns a hash of some of the ticket data 5197calculated based on ticket history info at the given date. 5198 5199 my %HistoryData = $TicketObject->HistoryTicketGet( 5200 StopYear => 2003, 5201 StopMonth => 12, 5202 StopDay => 24, 5203 StopHour => 10, (optional, default 23) 5204 StopMinute => 0, (optional, default 59) 5205 StopSecond => 0, (optional, default 59) 5206 TicketID => 123, 5207 Force => 0, # 1: don't use cache 5208 ); 5209 5210returns 5211 5212 TicketNumber 5213 TicketID 5214 Type 5215 TypeID 5216 Queue 5217 QueueID 5218 Priority 5219 PriorityID 5220 State 5221 StateID 5222 Owner 5223 OwnerID 5224 CreateUserID 5225 CreateTime (timestamp) 5226 CreateOwnerID 5227 CreatePriority 5228 CreatePriorityID 5229 CreateState 5230 CreateStateID 5231 CreateQueue 5232 CreateQueueID 5233 LockFirst (timestamp) 5234 LockLast (timestamp) 5235 UnlockFirst (timestamp) 5236 UnlockLast (timestamp) 5237 5238=cut 5239 5240sub HistoryTicketGet { 5241 my ( $Self, %Param ) = @_; 5242 5243 # check needed stuff 5244 for my $Needed (qw(TicketID StopYear StopMonth StopDay)) { 5245 if ( !$Param{$Needed} ) { 5246 $Kernel::OM->Get('Kernel::System::Log')->Log( 5247 Priority => 'error', 5248 Message => "Need $Needed!" 5249 ); 5250 return; 5251 } 5252 } 5253 $Param{StopHour} = defined $Param{StopHour} ? $Param{StopHour} : '23'; 5254 $Param{StopMinute} = defined $Param{StopMinute} ? $Param{StopMinute} : '59'; 5255 $Param{StopSecond} = defined $Param{StopSecond} ? $Param{StopSecond} : '59'; 5256 5257 # format month and day params 5258 for my $DateParameter (qw(StopMonth StopDay)) { 5259 $Param{$DateParameter} = sprintf( "%02d", $Param{$DateParameter} ); 5260 } 5261 5262 my $CacheKey = 'HistoryTicketGet::' 5263 . join( '::', map { ( $_ || 0 ) . "::$Param{$_}" } sort keys %Param ); 5264 5265 my $Cached = $Kernel::OM->Get('Kernel::System::Cache')->Get( 5266 Type => $Self->{CacheType}, 5267 Key => $CacheKey, 5268 ); 5269 if ( ref $Cached eq 'HASH' && !$Param{Force} ) { 5270 return %{$Cached}; 5271 } 5272 5273 my $Time 5274 = "$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} $Param{StopHour}:$Param{StopMinute}:$Param{StopSecond}"; 5275 5276 # get database object 5277 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 5278 5279 return if !$DBObject->Prepare( 5280 SQL => ' 5281 SELECT th.name, tht.name, th.create_time, th.create_by, th.ticket_id, 5282 th.article_id, th.queue_id, th.state_id, th.priority_id, th.owner_id, th.type_id 5283 FROM ticket_history th, ticket_history_type tht 5284 WHERE th.history_type_id = tht.id 5285 AND th.ticket_id = ? 5286 AND th.create_time <= ? 5287 ORDER BY th.create_time, th.id ASC', 5288 Bind => [ \$Param{TicketID}, \$Time ], 5289 Limit => 3000, 5290 ); 5291 5292 my %Ticket; 5293 while ( my @Row = $DBObject->FetchrowArray() ) { 5294 5295 if ( $Row[1] eq 'NewTicket' ) { 5296 if ( 5297 $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)$/ 5298 || $Row[0] =~ /Ticket=\[(.+?)\],.+?Q\=(.+?);P\=(.+?);S\=(.+?)/ 5299 ) 5300 { 5301 $Ticket{TicketNumber} = $1; 5302 $Ticket{Queue} = $2; 5303 $Ticket{CreateQueue} = $2; 5304 $Ticket{Priority} = $3; 5305 $Ticket{CreatePriority} = $3; 5306 $Ticket{State} = $4; 5307 $Ticket{CreateState} = $4; 5308 $Ticket{TicketID} = $Row[4]; 5309 $Ticket{Owner} = 'root'; 5310 $Ticket{CreateUserID} = $Row[3]; 5311 $Ticket{CreateTime} = $Row[2]; 5312 } 5313 else { 5314 5315 # COMPAT: compat to 1.1 5316 # NewTicket 5317 $Ticket{TicketVersion} = '1.1'; 5318 $Ticket{TicketID} = $Row[4]; 5319 $Ticket{CreateUserID} = $Row[3]; 5320 $Ticket{CreateTime} = $Row[2]; 5321 } 5322 $Ticket{CreateOwnerID} = $Row[9] || ''; 5323 $Ticket{CreatePriorityID} = $Row[8] || ''; 5324 $Ticket{CreateStateID} = $Row[7] || ''; 5325 $Ticket{CreateQueueID} = $Row[6] || ''; 5326 } 5327 5328 # COMPAT: compat to 1.1 5329 elsif ( $Row[1] eq 'PhoneCallCustomer' ) { 5330 $Ticket{TicketVersion} = '1.1'; 5331 $Ticket{TicketID} = $Row[4]; 5332 $Ticket{CreateUserID} = $Row[3]; 5333 $Ticket{CreateTime} = $Row[2]; 5334 } 5335 elsif ( $Row[1] eq 'Move' ) { 5336 if ( 5337 $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ 5338 || $Row[0] =~ /^Ticket moved to Queue '(.+?)'/ 5339 ) 5340 { 5341 $Ticket{Queue} = $1; 5342 } 5343 } 5344 elsif ( 5345 $Row[1] eq 'StateUpdate' 5346 || $Row[1] eq 'Close successful' 5347 || $Row[1] eq 'Close unsuccessful' 5348 || $Row[1] eq 'Open' 5349 || $Row[1] eq 'Misc' 5350 ) 5351 { 5352 if ( 5353 $Row[0] =~ /^\%\%(.+?)\%\%(.+?)(\%\%|)$/ 5354 || $Row[0] =~ /^Old: '(.+?)' New: '(.+?)'/ 5355 || $Row[0] =~ /^Changed Ticket State from '(.+?)' to '(.+?)'/ 5356 ) 5357 { 5358 $Ticket{State} = $2; 5359 $Ticket{StateTime} = $Row[2]; 5360 } 5361 } 5362 elsif ( $Row[1] eq 'TicketFreeTextUpdate' ) { 5363 if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)$/ ) { 5364 $Ticket{ 'Ticket' . $1 } = $2; 5365 $Ticket{ 'Ticket' . $3 } = $4; 5366 $Ticket{$1} = $2; 5367 $Ticket{$3} = $4; 5368 } 5369 } 5370 elsif ( $Row[1] eq 'TicketDynamicFieldUpdate' ) { 5371 5372 # take care about different values between 3.3 and 4 5373 # 3.x: %%FieldName%%test%%Value%%TestValue1 5374 # 4.x: %%FieldName%%test%%Value%%TestValue1%%OldValue%%OldTestValue1 5375 if ( $Row[0] =~ /^\%\%FieldName\%\%(.+?)\%\%Value\%\%(.*?)(?:\%\%|$)/ ) { 5376 5377 my $FieldName = $1; 5378 my $Value = $2 || ''; 5379 $Ticket{$FieldName} = $Value; 5380 5381 # Backward compatibility for TicketFreeText and TicketFreeTime 5382 if ( $FieldName =~ /^Ticket(Free(?:Text|Key)(?:[?:1[0-6]|[1-9]))$/ ) { 5383 5384 # Remove the leading Ticket on field name 5385 my $FreeFieldName = $1; 5386 $Ticket{$FreeFieldName} = $Value; 5387 } 5388 } 5389 } 5390 elsif ( $Row[1] eq 'PriorityUpdate' ) { 5391 if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ ) { 5392 $Ticket{Priority} = $3; 5393 } 5394 } 5395 elsif ( $Row[1] eq 'OwnerUpdate' ) { 5396 if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)/ || $Row[0] =~ /^New Owner is '(.+?)'/ ) { 5397 $Ticket{Owner} = $1; 5398 } 5399 } 5400 elsif ( $Row[1] eq 'Lock' ) { 5401 if ( !$Ticket{LockFirst} ) { 5402 $Ticket{LockFirst} = $Row[2]; 5403 } 5404 $Ticket{LockLast} = $Row[2]; 5405 } 5406 elsif ( $Row[1] eq 'Unlock' ) { 5407 if ( !$Ticket{UnlockFirst} ) { 5408 $Ticket{UnlockFirst} = $Row[2]; 5409 } 5410 $Ticket{UnlockLast} = $Row[2]; 5411 } 5412 5413 # get default options 5414 $Ticket{TypeID} = $Row[10] || ''; 5415 $Ticket{OwnerID} = $Row[9] || ''; 5416 $Ticket{PriorityID} = $Row[8] || ''; 5417 $Ticket{StateID} = $Row[7] || ''; 5418 $Ticket{QueueID} = $Row[6] || ''; 5419 } 5420 if ( !%Ticket ) { 5421 $Kernel::OM->Get('Kernel::System::Log')->Log( 5422 Priority => 'notice', 5423 Message => "No such TicketID in ticket history till " 5424 . "'$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} $Param{StopHour}:$Param{StopMinute}:$Param{StopSecond}' ($Param{TicketID})!", 5425 ); 5426 return; 5427 } 5428 5429 # update old ticket info 5430 my %CurrentTicketData = $Self->TicketGet( 5431 TicketID => $Ticket{TicketID}, 5432 DynamicFields => 0, 5433 ); 5434 for my $TicketAttribute (qw(State Priority Queue TicketNumber)) { 5435 if ( !$Ticket{$TicketAttribute} ) { 5436 $Ticket{$TicketAttribute} = $CurrentTicketData{$TicketAttribute}; 5437 } 5438 if ( !$Ticket{"Create$TicketAttribute"} ) { 5439 $Ticket{"Create$TicketAttribute"} = $CurrentTicketData{$TicketAttribute}; 5440 } 5441 } 5442 5443 # create datetime object 5444 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 5445 5446 # check if we should cache this ticket data 5447 my $DateTimeValues = $DateTimeObject->Get(); 5448 5449 # if the request is for the last month or older, cache it 5450 if ( int $DateTimeValues->{Year} . int $DateTimeValues->{Month} > int $Param{StopYear} . int $Param{StopMonth} ) { 5451 $Kernel::OM->Get('Kernel::System::Cache')->Set( 5452 Type => $Self->{CacheType}, 5453 TTL => $Self->{CacheTTL}, 5454 Key => $CacheKey, 5455 Value => \%Ticket, 5456 ); 5457 } 5458 5459 return %Ticket; 5460} 5461 5462=head2 HistoryTypeLookup() 5463 5464returns the id of the requested history type. 5465 5466 my $ID = $TicketObject->HistoryTypeLookup( Type => 'Move' ); 5467 5468=cut 5469 5470sub HistoryTypeLookup { 5471 my ( $Self, %Param ) = @_; 5472 5473 # check needed stuff 5474 if ( !$Param{Type} ) { 5475 $Kernel::OM->Get('Kernel::System::Log')->Log( 5476 Priority => 'error', 5477 Message => 'Need Type!' 5478 ); 5479 return; 5480 } 5481 5482 # check if we ask the same request? 5483 my $CacheKey = 'Ticket::History::HistoryTypeLookup::' . $Param{Type}; 5484 my $Cached = $Kernel::OM->Get('Kernel::System::Cache')->Get( 5485 Type => $Self->{CacheType}, 5486 Key => $CacheKey, 5487 ); 5488 5489 if ($Cached) { 5490 return $Cached; 5491 } 5492 5493 # get database object 5494 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 5495 5496 # db query 5497 return if !$DBObject->Prepare( 5498 SQL => 'SELECT id FROM ticket_history_type WHERE name = ?', 5499 Bind => [ \$Param{Type} ], 5500 ); 5501 5502 my $HistoryTypeID; 5503 while ( my @Row = $DBObject->FetchrowArray() ) { 5504 $HistoryTypeID = $Row[0]; 5505 } 5506 5507 # check if data exists 5508 if ( !$HistoryTypeID ) { 5509 $Kernel::OM->Get('Kernel::System::Log')->Log( 5510 Priority => 'error', 5511 Message => "No TypeID for $Param{Type} found!", 5512 ); 5513 return; 5514 } 5515 5516 # set cache 5517 $Kernel::OM->Get('Kernel::System::Cache')->Set( 5518 Type => $Self->{CacheType}, 5519 TTL => $Self->{CacheTTL}, 5520 Key => $CacheKey, 5521 Value => $HistoryTypeID, 5522 CacheInMemory => 1, 5523 CacheInBackend => 0, 5524 ); 5525 5526 return $HistoryTypeID; 5527} 5528 5529=head2 HistoryAdd() 5530 5531add a history entry to an ticket 5532 5533 my $Success = $TicketObject->HistoryAdd( 5534 Name => 'Some Comment', 5535 HistoryType => 'Move', # see system tables 5536 TicketID => 123, 5537 ArticleID => 1234, # not required! 5538 QueueID => 123, # not required! 5539 TypeID => 123, # not required! 5540 CreateUserID => 123, 5541 ); 5542 5543Events: 5544 HistoryAdd 5545 5546=cut 5547 5548sub HistoryAdd { 5549 my ( $Self, %Param ) = @_; 5550 5551 # check needed stuff 5552 if ( !$Param{Name} ) { 5553 $Kernel::OM->Get('Kernel::System::Log')->Log( 5554 Priority => 'error', 5555 Message => 'Need Name!' 5556 ); 5557 return; 5558 } 5559 5560 # lookup! 5561 if ( !$Param{HistoryTypeID} && $Param{HistoryType} ) { 5562 $Param{HistoryTypeID} = $Self->HistoryTypeLookup( Type => $Param{HistoryType} ); 5563 } 5564 5565 # check needed stuff 5566 for my $Needed (qw(TicketID CreateUserID HistoryTypeID)) { 5567 if ( !$Param{$Needed} ) { 5568 $Kernel::OM->Get('Kernel::System::Log')->Log( 5569 Priority => 'error', 5570 Message => "Need $Needed!" 5571 ); 5572 return; 5573 } 5574 } 5575 5576 my %Ticket; 5577 if ( !$Param{QueueID} || !$Param{TypeID} || !$Param{OwnerID} || !$Param{PriorityID} || !$Param{StateID} ) { 5578 %Ticket = $Self->TicketGet( 5579 %Param, 5580 DynamicFields => 0, 5581 ); 5582 } 5583 5584 if ( !$Param{QueueID} ) { 5585 $Param{QueueID} = $Ticket{QueueID}; 5586 } 5587 if ( !$Param{TypeID} ) { 5588 $Param{TypeID} = $Ticket{TypeID}; 5589 } 5590 if ( !$Param{OwnerID} ) { 5591 $Param{OwnerID} = $Ticket{OwnerID}; 5592 } 5593 if ( !$Param{PriorityID} ) { 5594 $Param{PriorityID} = $Ticket{PriorityID}; 5595 } 5596 if ( !$Param{StateID} ) { 5597 $Param{StateID} = $Ticket{StateID}; 5598 } 5599 5600 # limit name to 200 chars 5601 if ( $Param{Name} ) { 5602 $Param{Name} = substr( $Param{Name}, 0, 200 ); 5603 } 5604 5605 # db quote 5606 if ( !$Param{ArticleID} ) { 5607 $Param{ArticleID} = undef; 5608 } 5609 5610 # db insert 5611 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 5612 SQL => 'INSERT INTO ticket_history ' 5613 . ' (name, history_type_id, ticket_id, article_id, queue_id, owner_id, ' 5614 . ' priority_id, state_id, type_id, ' 5615 . ' create_time, create_by, change_time, change_by) ' 5616 . 'VALUES ' 5617 . '(?, ?, ?, ?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)', 5618 Bind => [ 5619 \$Param{Name}, \$Param{HistoryTypeID}, \$Param{TicketID}, \$Param{ArticleID}, 5620 \$Param{QueueID}, \$Param{OwnerID}, \$Param{PriorityID}, \$Param{StateID}, 5621 \$Param{TypeID}, \$Param{CreateUserID}, \$Param{CreateUserID}, 5622 ], 5623 ); 5624 5625 # Prevent infinite loops for notifications base on 'HistoryAdd' event 5626 # see bug#13002 5627 if ( $Param{HistoryType} ne 'SendAgentNotification' ) { 5628 5629 # trigger event 5630 $Self->EventHandler( 5631 Event => 'HistoryAdd', 5632 Data => { 5633 TicketID => $Param{TicketID}, 5634 }, 5635 UserID => $Param{CreateUserID}, 5636 ); 5637 } 5638 5639 return 1; 5640} 5641 5642=head2 HistoryGet() 5643 5644get ticket history as array with hashes 5645(TicketID, ArticleID, Name, CreateBy, CreateTime, HistoryType, QueueID, 5646OwnerID, PriorityID, StateID, HistoryTypeID and TypeID) 5647 5648 my @HistoryLines = $TicketObject->HistoryGet( 5649 TicketID => 123, 5650 UserID => 123, 5651 ); 5652 5653=cut 5654 5655sub HistoryGet { 5656 my ( $Self, %Param ) = @_; 5657 5658 my @Lines; 5659 5660 # check needed stuff 5661 for my $Needed (qw(TicketID UserID)) { 5662 if ( !$Param{$Needed} ) { 5663 $Kernel::OM->Get('Kernel::System::Log')->Log( 5664 Priority => 'error', 5665 Message => "Need $Needed!" 5666 ); 5667 return; 5668 } 5669 } 5670 5671 # get database object 5672 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 5673 5674 return if !$DBObject->Prepare( 5675 SQL => 'SELECT sh.name, sh.article_id, sh.create_time, sh.create_by, ht.name, ' 5676 . ' sh.queue_id, sh.owner_id, sh.priority_id, sh.state_id, sh.history_type_id, sh.type_id ' 5677 . ' FROM ticket_history sh, ticket_history_type ht WHERE ' 5678 . ' sh.ticket_id = ? AND ht.id = sh.history_type_id' 5679 . ' ORDER BY sh.create_time, sh.id', 5680 Bind => [ \$Param{TicketID} ], 5681 ); 5682 5683 while ( my @Row = $DBObject->FetchrowArray() ) { 5684 my %Data; 5685 $Data{TicketID} = $Param{TicketID}; 5686 $Data{ArticleID} = $Row[1] || 0; 5687 $Data{Name} = $Row[0]; 5688 $Data{CreateBy} = $Row[3]; 5689 $Data{CreateTime} = $Row[2]; 5690 $Data{HistoryType} = $Row[4]; 5691 $Data{QueueID} = $Row[5]; 5692 $Data{OwnerID} = $Row[6]; 5693 $Data{PriorityID} = $Row[7]; 5694 $Data{StateID} = $Row[8]; 5695 $Data{HistoryTypeID} = $Row[9]; 5696 $Data{TypeID} = $Row[10]; 5697 push @Lines, \%Data; 5698 } 5699 5700 # get user object 5701 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 5702 5703 # get user data 5704 for my $Data (@Lines) { 5705 5706 my %UserInfo = $UserObject->GetUserData( 5707 UserID => $Data->{CreateBy}, 5708 ); 5709 5710 # merge result, put %Data last so that it "wins" 5711 %{$Data} = ( %UserInfo, %{$Data} ); 5712 } 5713 5714 return @Lines; 5715} 5716 5717=head2 HistoryDelete() 5718 5719delete a ticket history (from storage) 5720 5721 my $Success = $TicketObject->HistoryDelete( 5722 TicketID => 123, 5723 UserID => 123, 5724 ); 5725 5726Events: 5727 HistoryDelete 5728 5729=cut 5730 5731sub HistoryDelete { 5732 my ( $Self, %Param ) = @_; 5733 5734 # check needed stuff 5735 for my $Needed (qw(TicketID UserID)) { 5736 if ( !$Param{$Needed} ) { 5737 $Kernel::OM->Get('Kernel::System::Log')->Log( 5738 Priority => 'error', 5739 Message => "Need $Needed!" 5740 ); 5741 return; 5742 } 5743 } 5744 5745 # delete ticket history entries from db 5746 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 5747 SQL => 5748 'DELETE FROM ticket_history WHERE ticket_id = ? AND (article_id IS NULL OR article_id = 0)', 5749 Bind => [ \$Param{TicketID} ], 5750 ); 5751 5752 # trigger event 5753 $Self->EventHandler( 5754 Event => 'HistoryDelete', 5755 Data => { 5756 TicketID => $Param{TicketID}, 5757 }, 5758 UserID => $Param{UserID}, 5759 ); 5760 5761 return 1; 5762} 5763 5764=head2 TicketAccountedTimeGet() 5765 5766returns the accounted time of a ticket. 5767 5768 my $AccountedTime = $TicketObject->TicketAccountedTimeGet(TicketID => 1234); 5769 5770=cut 5771 5772sub TicketAccountedTimeGet { 5773 my ( $Self, %Param ) = @_; 5774 5775 # check needed stuff 5776 if ( !$Param{TicketID} ) { 5777 $Kernel::OM->Get('Kernel::System::Log')->Log( 5778 Priority => 'error', 5779 Message => 'Need TicketID!' 5780 ); 5781 return; 5782 } 5783 5784 # get database object 5785 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 5786 5787 # db query 5788 return if !$DBObject->Prepare( 5789 SQL => 'SELECT time_unit FROM time_accounting WHERE ticket_id = ?', 5790 Bind => [ \$Param{TicketID} ], 5791 ); 5792 5793 my $AccountedTime = 0; 5794 while ( my @Row = $DBObject->FetchrowArray() ) { 5795 $Row[0] =~ s/,/./g; 5796 $AccountedTime = $AccountedTime + $Row[0]; 5797 } 5798 5799 return $AccountedTime; 5800} 5801 5802=head2 TicketAccountTime() 5803 5804account time to a ticket. 5805 5806 my $Success = $TicketObject->TicketAccountTime( 5807 TicketID => 1234, 5808 ArticleID => 23542, 5809 TimeUnit => '4.5', 5810 UserID => 1, 5811 ); 5812 5813Events: 5814 TicketAccountTime 5815 5816=cut 5817 5818sub TicketAccountTime { 5819 my ( $Self, %Param ) = @_; 5820 5821 # check needed stuff 5822 for my $Needed (qw(TicketID ArticleID TimeUnit UserID)) { 5823 if ( !$Param{$Needed} ) { 5824 $Kernel::OM->Get('Kernel::System::Log')->Log( 5825 Priority => 'error', 5826 Message => "Need $Needed!" 5827 ); 5828 return; 5829 } 5830 } 5831 5832 # check some wrong formats 5833 $Param{TimeUnit} =~ s/,/\./g; 5834 $Param{TimeUnit} =~ s/ //g; 5835 $Param{TimeUnit} =~ s/^(\d{1,10}\.\d\d).+?$/$1/g; 5836 chomp $Param{TimeUnit}; 5837 5838 # get database object 5839 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 5840 5841 # update change time 5842 return if !$DBObject->Do( 5843 SQL => 'UPDATE ticket SET change_time = current_timestamp, ' 5844 . ' change_by = ? WHERE id = ?', 5845 Bind => [ \$Param{UserID}, \$Param{TicketID} ], 5846 ); 5847 5848 # db quote 5849 $Param{TimeUnit} = $DBObject->Quote( $Param{TimeUnit}, 'Number' ); 5850 5851 # db update 5852 return if !$DBObject->Do( 5853 SQL => "INSERT INTO time_accounting " 5854 . " (ticket_id, article_id, time_unit, create_time, create_by, change_time, change_by) " 5855 . " VALUES (?, ?, $Param{TimeUnit}, current_timestamp, ?, current_timestamp, ?)", 5856 Bind => [ 5857 \$Param{TicketID}, \$Param{ArticleID}, \$Param{UserID}, \$Param{UserID}, 5858 ], 5859 ); 5860 5861 # clear ticket cache 5862 $Self->_TicketCacheClear( TicketID => $Param{TicketID} ); 5863 5864 # add history 5865 my $AccountedTime = $Self->TicketAccountedTimeGet( TicketID => $Param{TicketID} ); 5866 $Self->HistoryAdd( 5867 TicketID => $Param{TicketID}, 5868 ArticleID => $Param{ArticleID}, 5869 CreateUserID => $Param{UserID}, 5870 HistoryType => 'TimeAccounting', 5871 Name => "\%\%$Param{TimeUnit}\%\%$AccountedTime", 5872 ); 5873 5874 # trigger event 5875 $Self->EventHandler( 5876 Event => 'TicketAccountTime', 5877 Data => { 5878 TicketID => $Param{TicketID}, 5879 ArticleID => $Param{ArticleID}, 5880 }, 5881 UserID => $Param{UserID}, 5882 ); 5883 5884 return 1; 5885} 5886 5887=head2 TicketMerge() 5888 5889merge two tickets 5890 5891 my $Success = $TicketObject->TicketMerge( 5892 MainTicketID => 412, 5893 MergeTicketID => 123, 5894 UserID => 123, 5895 ); 5896 5897Events: 5898 TicketMerge 5899 5900=cut 5901 5902sub TicketMerge { 5903 my ( $Self, %Param ) = @_; 5904 5905 # check needed stuff 5906 for my $Needed (qw(MainTicketID MergeTicketID UserID)) { 5907 if ( !$Param{$Needed} ) { 5908 $Kernel::OM->Get('Kernel::System::Log')->Log( 5909 Priority => 'error', 5910 Message => "Need $Needed!" 5911 ); 5912 return; 5913 } 5914 } 5915 5916 # Get the list of all merged states. 5917 my @MergeStateList = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType( 5918 StateType => ['merged'], 5919 Result => 'Name', 5920 ); 5921 5922 # Error handling. 5923 if ( !@MergeStateList ) { 5924 $Kernel::OM->Get('Kernel::System::Log')->Log( 5925 Priority => 'error', 5926 Message => "No merge state found! Please add a valid merge state.", 5927 ); 5928 return 'NoValidMergeStates'; 5929 } 5930 5931 # get database object 5932 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 5933 5934 # change ticket id of merge ticket to main ticket 5935 return if !$DBObject->Do( 5936 SQL => 'UPDATE article SET ticket_id = ?, change_time = current_timestamp, ' 5937 . ' change_by = ? WHERE ticket_id = ?', 5938 Bind => [ \$Param{MainTicketID}, \$Param{UserID}, \$Param{MergeTicketID} ], 5939 ); 5940 5941 # former bug 9635 (with table article_index) 5942 # do the same with article_search_index (harmless if not used) 5943 return if !$DBObject->Do( 5944 SQL => 'UPDATE article_search_index SET ticket_id = ? WHERE ticket_id = ?', 5945 Bind => [ \$Param{MainTicketID}, \$Param{MergeTicketID} ], 5946 ); 5947 5948 # reassign article history 5949 return if !$DBObject->Do( 5950 SQL => 'UPDATE ticket_history SET ticket_id = ?, change_time = current_timestamp, ' 5951 . ' change_by = ? WHERE ticket_id = ? 5952 AND (article_id IS NOT NULL OR article_id != 0)', 5953 Bind => [ \$Param{MainTicketID}, \$Param{UserID}, \$Param{MergeTicketID} ], 5954 ); 5955 5956 # update the accounted time of the main ticket 5957 return if !$DBObject->Do( 5958 SQL => 'UPDATE time_accounting SET ticket_id = ?, change_time = current_timestamp, ' 5959 . ' change_by = ? WHERE ticket_id = ?', 5960 Bind => [ \$Param{MainTicketID}, \$Param{UserID}, \$Param{MergeTicketID} ], 5961 ); 5962 5963 my %MainTicket = $Self->TicketGet( 5964 TicketID => $Param{MainTicketID}, 5965 DynamicFields => 0, 5966 ); 5967 my %MergeTicket = $Self->TicketGet( 5968 TicketID => $Param{MergeTicketID}, 5969 DynamicFields => 0, 5970 ); 5971 5972 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 5973 5974 # Set language for AutomaticMergeText, see more in bug #13967 5975 my %UserInfo = $Kernel::OM->Get('Kernel::System::User')->GetUserData( 5976 UserID => $Param{UserID}, 5977 ); 5978 my $Language = $UserInfo{UserLanguage} || $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage') || 'en'; 5979 5980 $Kernel::OM->ObjectsDiscard( 5981 Objects => ['Kernel::Language'], 5982 ); 5983 $Kernel::OM->ObjectParamAdd( 5984 'Kernel::Language' => { 5985 UserLanguage => $Language, 5986 }, 5987 ); 5988 my $LanguageObject = $Kernel::OM->Get('Kernel::Language'); 5989 5990 my $Body = $ConfigObject->Get('Ticket::Frontend::AutomaticMergeText'); 5991 $Body = $LanguageObject->Translate($Body); 5992 $Body =~ s{<OTRS_TICKET>}{$MergeTicket{TicketNumber}}xms; 5993 $Body =~ s{<OTRS_MERGE_TO_TICKET>}{$MainTicket{TicketNumber}}xms; 5994 5995 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 5996 5997 # Add merge article to merge ticket using internal channel. 5998 $ArticleObject->BackendForChannel( ChannelName => 'Internal' )->ArticleCreate( 5999 TicketID => $Param{MergeTicketID}, 6000 SenderType => 'agent', 6001 IsVisibleForCustomer => 1, 6002 ContentType => "text/plain; charset=ascii", 6003 UserID => $Param{UserID}, 6004 HistoryType => 'AddNote', 6005 HistoryComment => '%%Note', 6006 Subject => $ConfigObject->Get('Ticket::Frontend::AutomaticMergeSubject') || 'Ticket Merged', 6007 Body => $Body, 6008 NoAgentNotify => 1, 6009 ); 6010 6011 # add merge history to merge ticket 6012 $Self->HistoryAdd( 6013 TicketID => $Param{MergeTicketID}, 6014 HistoryType => 'Merged', 6015 Name => "\%\%$MergeTicket{TicketNumber}\%\%$Param{MergeTicketID}" 6016 . "\%\%$MainTicket{TicketNumber}\%\%$Param{MainTicketID}", 6017 CreateUserID => $Param{UserID}, 6018 ); 6019 6020 # add merge history to main ticket 6021 $Self->HistoryAdd( 6022 TicketID => $Param{MainTicketID}, 6023 HistoryType => 'Merged', 6024 Name => "\%\%$MergeTicket{TicketNumber}\%\%$Param{MergeTicketID}" 6025 . "\%\%$MainTicket{TicketNumber}\%\%$Param{MainTicketID}", 6026 CreateUserID => $Param{UserID}, 6027 ); 6028 6029 # transfer watchers - only those that were not already watching the main ticket 6030 # delete all watchers from the merge ticket that are already watching the main ticket 6031 my %MainWatchers = $Self->TicketWatchGet( 6032 TicketID => $Param{MainTicketID}, 6033 ); 6034 6035 my %MergeWatchers = $Self->TicketWatchGet( 6036 TicketID => $Param{MergeTicketID}, 6037 ); 6038 6039 WATCHER: 6040 for my $WatcherID ( sort keys %MergeWatchers ) { 6041 6042 next WATCHER if !$MainWatchers{$WatcherID}; 6043 return if !$DBObject->Do( 6044 SQL => ' 6045 DELETE FROM ticket_watcher 6046 WHERE user_id = ? 6047 AND ticket_id = ? 6048 ', 6049 Bind => [ \$WatcherID, \$Param{MergeTicketID} ], 6050 ); 6051 } 6052 6053 # transfer remaining watchers to new ticket 6054 return if !$DBObject->Do( 6055 SQL => ' 6056 UPDATE ticket_watcher 6057 SET ticket_id = ? 6058 WHERE ticket_id = ? 6059 ', 6060 Bind => [ \$Param{MainTicketID}, \$Param{MergeTicketID} ], 6061 ); 6062 6063 # transfer all linked objects to new ticket 6064 $Self->TicketMergeLinkedObjects( 6065 MergeTicketID => $Param{MergeTicketID}, 6066 MainTicketID => $Param{MainTicketID}, 6067 UserID => $Param{UserID}, 6068 ); 6069 6070 # link tickets 6071 $Kernel::OM->Get('Kernel::System::LinkObject')->LinkAdd( 6072 SourceObject => 'Ticket', 6073 SourceKey => $Param{MainTicketID}, 6074 TargetObject => 'Ticket', 6075 TargetKey => $Param{MergeTicketID}, 6076 Type => 'ParentChild', 6077 State => 'Valid', 6078 UserID => $Param{UserID}, 6079 ); 6080 6081 # Update change time and user ID for main ticket. 6082 # See bug#13092 for more information. 6083 return if !$DBObject->Do( 6084 SQL => 'UPDATE ticket SET change_time = current_timestamp, change_by = ? WHERE id = ?', 6085 Bind => [ \$Param{UserID}, \$Param{MainTicketID} ], 6086 ); 6087 6088 # set new state of merge ticket 6089 $Self->TicketStateSet( 6090 State => $MergeStateList[0], 6091 TicketID => $Param{MergeTicketID}, 6092 UserID => $Param{UserID}, 6093 ); 6094 6095 # unlock ticket 6096 $Self->LockSet( 6097 Lock => 'unlock', 6098 TicketID => $Param{MergeTicketID}, 6099 UserID => $Param{UserID}, 6100 ); 6101 6102 # remove seen flag for all users on the main ticket 6103 $Self->TicketFlagDelete( 6104 TicketID => $Param{MainTicketID}, 6105 Key => 'Seen', 6106 AllUsers => 1, 6107 ); 6108 6109 $Self->TicketMergeDynamicFields( 6110 MergeTicketID => $Param{MergeTicketID}, 6111 MainTicketID => $Param{MainTicketID}, 6112 UserID => $Param{UserID}, 6113 ); 6114 6115 $Self->_TicketCacheClear( TicketID => $Param{MergeTicketID} ); 6116 $Self->_TicketCacheClear( TicketID => $Param{MainTicketID} ); 6117 6118 # trigger event 6119 $Self->EventHandler( 6120 Event => 'TicketMerge', 6121 Data => { 6122 TicketID => $Param{MergeTicketID}, 6123 MainTicketID => $Param{MainTicketID}, 6124 }, 6125 UserID => $Param{UserID}, 6126 ); 6127 6128 return 1; 6129} 6130 6131=head2 TicketMergeDynamicFields() 6132 6133merge dynamic fields from one ticket into another, that is, copy 6134them from the merge ticket to the main ticket if the value is empty 6135in the main ticket. 6136 6137 my $Success = $TicketObject->TicketMergeDynamicFields( 6138 MainTicketID => 123, 6139 MergeTicketID => 42, 6140 UserID => 1, 6141 DynamicFields => ['DynamicField_TicketFreeText1'], # optional 6142 ); 6143 6144If DynamicFields is not present, it is taken from the Ticket::MergeDynamicFields 6145configuration. 6146 6147=cut 6148 6149sub TicketMergeDynamicFields { 6150 my ( $Self, %Param ) = @_; 6151 6152 for my $Needed (qw(MainTicketID MergeTicketID UserID)) { 6153 if ( !$Param{$Needed} ) { 6154 $Kernel::OM->Get('Kernel::System::Log')->Log( 6155 Priority => 'error', 6156 Message => "Need $Needed!" 6157 ); 6158 return; 6159 } 6160 } 6161 6162 my $DynamicFields = $Param{DynamicFields}; 6163 6164 if ( !$DynamicFields ) { 6165 $DynamicFields = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::MergeDynamicFields'); 6166 } 6167 6168 return 1 if !IsArrayRefWithData($DynamicFields); 6169 6170 my %MainTicket = $Self->TicketGet( 6171 TicketID => $Param{MainTicketID}, 6172 UserID => $Param{UserID}, 6173 DynamicFields => 1, 6174 ); 6175 my %MergeTicket = $Self->TicketGet( 6176 TicketID => $Param{MergeTicketID}, 6177 UserID => $Param{UserID}, 6178 DynamicFields => 1, 6179 ); 6180 6181 # get dynamic field objects 6182 my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); 6183 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 6184 6185 FIELDS: 6186 for my $DynamicFieldName ( @{$DynamicFields} ) { 6187 6188 my $Key = "DynamicField_$DynamicFieldName"; 6189 6190 if ( 6191 defined $MergeTicket{$Key} 6192 && length $MergeTicket{$Key} 6193 && !( defined $MainTicket{$Key} && length $MainTicket{$Key} ) 6194 ) 6195 { 6196 6197 my $DynamicFieldConfig = $DynamicFieldObject->DynamicFieldGet( 6198 Name => $DynamicFieldName, 6199 ); 6200 6201 if ( !$DynamicFieldConfig ) { 6202 $Kernel::OM->Get('Kernel::System::Log')->Log( 6203 Priority => 'Error', 6204 Message => qq[No such dynamic field "$DynamicFieldName"], 6205 ); 6206 return; 6207 } 6208 6209 $DynamicFieldBackendObject->ValueSet( 6210 DynamicFieldConfig => $DynamicFieldConfig, 6211 ObjectID => $Param{MainTicketID}, 6212 UserID => $Param{UserID}, 6213 Value => $MergeTicket{$Key}, 6214 ); 6215 } 6216 } 6217 6218 return 1; 6219} 6220 6221=head2 TicketMergeLinkedObjects() 6222 6223merge linked objects from one ticket into another, that is, move 6224them from the merge ticket to the main ticket in the link_relation table. 6225 6226 my $Success = $TicketObject->TicketMergeLinkedObjects( 6227 MainTicketID => 123, 6228 MergeTicketID => 42, 6229 UserID => 1, 6230 ); 6231 6232=cut 6233 6234sub TicketMergeLinkedObjects { 6235 my ( $Self, %Param ) = @_; 6236 6237 for my $Needed (qw(MainTicketID MergeTicketID UserID)) { 6238 if ( !$Param{$Needed} ) { 6239 $Kernel::OM->Get('Kernel::System::Log')->Log( 6240 Priority => 'error', 6241 Message => "Need $Needed!", 6242 ); 6243 return; 6244 } 6245 } 6246 6247 # Lookup the object id of a ticket. 6248 my $TicketObjectID = $Kernel::OM->Get('Kernel::System::LinkObject')->ObjectLookup( 6249 Name => 'Ticket', 6250 ); 6251 6252 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 6253 6254 # Delete all duplicate links relations between merged tickets. 6255 # See bug#12994 (https://bugs.otrs.org/show_bug.cgi?id=12994). 6256 $DBObject->Prepare( 6257 SQL => ' 6258 SELECT target_key 6259 FROM link_relation 6260 WHERE target_object_id = ? 6261 AND source_object_id = ? 6262 AND source_key= ? 6263 AND target_key 6264 IN (SELECT target_key FROM link_relation WHERE source_key= ? )', 6265 Bind => [ 6266 \$TicketObjectID, 6267 \$TicketObjectID, 6268 \$Param{MainTicketID}, 6269 \$Param{MergeTicketID}, 6270 ], 6271 ); 6272 6273 my @Relations; 6274 while ( my @Row = $DBObject->FetchrowArray() ) { 6275 push @Relations, $Row[0]; 6276 } 6277 if (@Relations) { 6278 6279 my $SQL = "DELETE FROM link_relation 6280 WHERE target_object_id = ? 6281 AND source_object_id = ? 6282 AND source_key = ? 6283 AND target_key IN ( '${\(join '\',\'', @Relations)}' )"; 6284 6285 $DBObject->Prepare( 6286 SQL => $SQL, 6287 Bind => [ 6288 \$TicketObjectID, 6289 \$TicketObjectID, 6290 \$Param{MergeTicketID}, 6291 ], 6292 ); 6293 } 6294 6295 # Update links from old ticket to new ticket where the old ticket is the source MainTicketID. 6296 $DBObject->Do( 6297 SQL => ' 6298 UPDATE link_relation 6299 SET source_key = ? 6300 WHERE source_object_id = ? 6301 AND source_key = ?', 6302 Bind => [ 6303 6304 \$Param{MainTicketID}, 6305 \$TicketObjectID, 6306 \$Param{MergeTicketID}, 6307 ], 6308 ); 6309 6310 # Update links from old ticket to new ticket where the old ticket is the target. 6311 $DBObject->Do( 6312 SQL => ' 6313 UPDATE link_relation 6314 SET target_key = ? 6315 WHERE target_object_id = ? 6316 AND target_key = ?', 6317 Bind => [ 6318 \$Param{MainTicketID}, 6319 \$TicketObjectID, 6320 \$Param{MergeTicketID}, 6321 ], 6322 ); 6323 6324 # Delete all links between tickets where source and target object are the same. 6325 $DBObject->Do( 6326 SQL => ' 6327 DELETE FROM link_relation 6328 WHERE source_object_id = ? 6329 AND target_object_id = ? 6330 AND source_key = target_key 6331 ', 6332 Bind => [ 6333 \$TicketObjectID, 6334 \$TicketObjectID, 6335 ], 6336 ); 6337 6338 return 1; 6339} 6340 6341=head2 TicketWatchGet() 6342 6343to get all user ids and additional attributes of an watched ticket 6344 6345 my %Watch = $TicketObject->TicketWatchGet( 6346 TicketID => 123, 6347 ); 6348 6349get list of users to notify 6350 6351 my %Watch = $TicketObject->TicketWatchGet( 6352 TicketID => 123, 6353 Notify => 1, 6354 ); 6355 6356get list of users as array 6357 6358 my @Watch = $TicketObject->TicketWatchGet( 6359 TicketID => 123, 6360 Result => 'ARRAY', 6361 ); 6362 6363=cut 6364 6365sub TicketWatchGet { 6366 my ( $Self, %Param ) = @_; 6367 6368 # check needed stuff 6369 if ( !$Param{TicketID} ) { 6370 $Kernel::OM->Get('Kernel::System::Log')->Log( 6371 Priority => 'error', 6372 Message => "Need TicketID!" 6373 ); 6374 return; 6375 } 6376 6377 # check if feature is enabled 6378 return if !$Kernel::OM->Get('Kernel::Config')->Get('Ticket::Watcher'); 6379 6380 # get database object 6381 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 6382 6383 # get all attributes of an watched ticket 6384 return if !$DBObject->Prepare( 6385 SQL => ' 6386 SELECT user_id, create_time, create_by, change_time, change_by 6387 FROM ticket_watcher 6388 WHERE ticket_id = ?', 6389 Bind => [ \$Param{TicketID} ], 6390 ); 6391 6392 # fetch the result 6393 my %Data; 6394 while ( my @Row = $DBObject->FetchrowArray() ) { 6395 $Data{ $Row[0] } = { 6396 CreateTime => $Row[1], 6397 CreateBy => $Row[2], 6398 ChangeTime => $Row[3], 6399 ChangeBy => $Row[4], 6400 }; 6401 } 6402 6403 if ( $Param{Notify} ) { 6404 6405 for my $UserID ( sort keys %Data ) { 6406 6407 # get user object 6408 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 6409 6410 my %UserData = $UserObject->GetUserData( 6411 UserID => $UserID, 6412 Valid => 1, 6413 ); 6414 6415 if ( !$UserData{UserSendWatcherNotification} ) { 6416 delete $Data{$UserID}; 6417 } 6418 } 6419 } 6420 6421 # check result 6422 if ( $Param{Result} && $Param{Result} eq 'ARRAY' ) { 6423 6424 my @UserIDs; 6425 6426 for my $UserID ( sort keys %Data ) { 6427 push @UserIDs, $UserID; 6428 } 6429 6430 return @UserIDs; 6431 } 6432 6433 return %Data; 6434} 6435 6436=head2 TicketWatchSubscribe() 6437 6438to subscribe a ticket to watch it 6439 6440 my $Success = $TicketObject->TicketWatchSubscribe( 6441 TicketID => 111, 6442 WatchUserID => 123, 6443 UserID => 123, 6444 ); 6445 6446Events: 6447 TicketSubscribe 6448 6449=cut 6450 6451sub TicketWatchSubscribe { 6452 my ( $Self, %Param ) = @_; 6453 6454 # check needed stuff 6455 for my $Needed (qw(TicketID WatchUserID UserID)) { 6456 if ( !defined $Param{$Needed} ) { 6457 $Kernel::OM->Get('Kernel::System::Log')->Log( 6458 Priority => 'error', 6459 Message => "Need $Needed!" 6460 ); 6461 return; 6462 } 6463 } 6464 6465 # get database object 6466 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 6467 6468 # db access 6469 return if !$DBObject->Do( 6470 SQL => ' 6471 DELETE FROM ticket_watcher 6472 WHERE ticket_id = ? 6473 AND user_id = ?', 6474 Bind => [ \$Param{TicketID}, \$Param{WatchUserID} ], 6475 ); 6476 return if !$DBObject->Do( 6477 SQL => ' 6478 INSERT INTO ticket_watcher (ticket_id, user_id, create_time, create_by, change_time, change_by) 6479 VALUES (?, ?, current_timestamp, ?, current_timestamp, ?)', 6480 Bind => [ \$Param{TicketID}, \$Param{WatchUserID}, \$Param{UserID}, \$Param{UserID} ], 6481 ); 6482 6483 # get user data 6484 my %User = $Kernel::OM->Get('Kernel::System::User')->GetUserData( 6485 UserID => $Param{WatchUserID}, 6486 ); 6487 6488 # add history 6489 $Self->HistoryAdd( 6490 TicketID => $Param{TicketID}, 6491 CreateUserID => $Param{UserID}, 6492 HistoryType => 'Subscribe', 6493 Name => "\%\%$User{UserFullname}", 6494 ); 6495 6496 # trigger event 6497 $Self->EventHandler( 6498 Event => 'TicketSubscribe', 6499 Data => { 6500 TicketID => $Param{TicketID}, 6501 }, 6502 UserID => $Param{UserID}, 6503 ); 6504 6505 return 1; 6506} 6507 6508=head2 TicketWatchUnsubscribe() 6509 6510to remove a subscription of a ticket 6511 6512 my $Success = $TicketObject->TicketWatchUnsubscribe( 6513 TicketID => 111, 6514 WatchUserID => 123, 6515 UserID => 123, 6516 ); 6517 6518Events: 6519 TicketUnsubscribe 6520 6521=cut 6522 6523sub TicketWatchUnsubscribe { 6524 my ( $Self, %Param ) = @_; 6525 6526 # check needed stuff 6527 for my $Needed (qw(TicketID UserID)) { 6528 if ( !defined $Param{$Needed} ) { 6529 $Kernel::OM->Get('Kernel::System::Log')->Log( 6530 Priority => 'error', 6531 Message => "Need $Needed!" 6532 ); 6533 return; 6534 } 6535 } 6536 6537 # only one of these parameters is needed 6538 if ( !$Param{WatchUserID} && !$Param{AllUsers} ) { 6539 $Kernel::OM->Get('Kernel::System::Log')->Log( 6540 Priority => 'error', 6541 Message => "Need WatchUserID or AllUsers param!" 6542 ); 6543 return; 6544 } 6545 6546 # get user object 6547 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 6548 6549 if ( $Param{AllUsers} ) { 6550 my @WatchUsers = $Self->TicketWatchGet( 6551 TicketID => $Param{TicketID}, 6552 Result => 'ARRAY', 6553 ); 6554 6555 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 6556 SQL => 'DELETE FROM ticket_watcher WHERE ticket_id = ?', 6557 Bind => [ \$Param{TicketID} ], 6558 ); 6559 6560 for my $WatchUser (@WatchUsers) { 6561 6562 my %User = $UserObject->GetUserData( 6563 UserID => $WatchUser, 6564 ); 6565 6566 $Self->HistoryAdd( 6567 TicketID => $Param{TicketID}, 6568 CreateUserID => $Param{UserID}, 6569 HistoryType => 'Unsubscribe', 6570 Name => "\%\%$User{UserFullname}", 6571 ); 6572 6573 $Self->EventHandler( 6574 Event => 'TicketUnsubscribe', 6575 Data => { 6576 TicketID => $Param{TicketID}, 6577 }, 6578 UserID => $Param{UserID}, 6579 ); 6580 } 6581 6582 } 6583 else { 6584 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 6585 SQL => 'DELETE FROM ticket_watcher WHERE ticket_id = ? AND user_id = ?', 6586 Bind => [ \$Param{TicketID}, \$Param{WatchUserID} ], 6587 ); 6588 6589 my %User = $UserObject->GetUserData( 6590 UserID => $Param{WatchUserID}, 6591 ); 6592 6593 $Self->HistoryAdd( 6594 TicketID => $Param{TicketID}, 6595 CreateUserID => $Param{UserID}, 6596 HistoryType => 'Unsubscribe', 6597 Name => "\%\%$User{UserFullname}", 6598 ); 6599 6600 $Self->EventHandler( 6601 Event => 'TicketUnsubscribe', 6602 Data => { 6603 TicketID => $Param{TicketID}, 6604 }, 6605 UserID => $Param{UserID}, 6606 ); 6607 } 6608 6609 return 1; 6610} 6611 6612=head2 TicketFlagSet() 6613 6614set ticket flags 6615 6616 my $Success = $TicketObject->TicketFlagSet( 6617 TicketID => 123, 6618 Key => 'Seen', 6619 Value => 1, 6620 UserID => 123, # apply to this user 6621 ); 6622 6623Events: 6624 TicketFlagSet 6625 6626=cut 6627 6628sub TicketFlagSet { 6629 my ( $Self, %Param ) = @_; 6630 6631 # check needed stuff 6632 for my $Needed (qw(TicketID Key Value UserID)) { 6633 if ( !defined $Param{$Needed} ) { 6634 $Kernel::OM->Get('Kernel::System::Log')->Log( 6635 Priority => 'error', 6636 Message => "Need $Needed!", 6637 ); 6638 return; 6639 } 6640 } 6641 6642 # get flags 6643 my %Flag = $Self->TicketFlagGet( 6644 TicketID => $Param{TicketID}, 6645 UserID => $Param{UserID}, 6646 ); 6647 6648 # check if set is needed 6649 return 1 if defined $Flag{ $Param{Key} } && $Flag{ $Param{Key} } eq $Param{Value}; 6650 6651 # get database object 6652 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 6653 6654 # set flag 6655 return if !$DBObject->Do( 6656 SQL => ' 6657 DELETE FROM ticket_flag 6658 WHERE ticket_id = ? 6659 AND ticket_key = ? 6660 AND create_by = ?', 6661 Bind => [ \$Param{TicketID}, \$Param{Key}, \$Param{UserID} ], 6662 ); 6663 return if !$DBObject->Do( 6664 SQL => ' 6665 INSERT INTO ticket_flag 6666 (ticket_id, ticket_key, ticket_value, create_time, create_by) 6667 VALUES (?, ?, ?, current_timestamp, ?)', 6668 Bind => [ \$Param{TicketID}, \$Param{Key}, \$Param{Value}, \$Param{UserID} ], 6669 ); 6670 6671 # delete cache 6672 $Kernel::OM->Get('Kernel::System::Cache')->Delete( 6673 Type => $Self->{CacheType}, 6674 Key => 'TicketFlag::' . $Param{TicketID}, 6675 ); 6676 6677 # event 6678 $Self->EventHandler( 6679 Event => 'TicketFlagSet', 6680 Data => { 6681 TicketID => $Param{TicketID}, 6682 Key => $Param{Key}, 6683 Value => $Param{Value}, 6684 UserID => $Param{UserID}, 6685 }, 6686 UserID => $Param{UserID}, 6687 ); 6688 6689 return 1; 6690} 6691 6692=head2 TicketFlagDelete() 6693 6694delete ticket flag 6695 6696 my $Success = $TicketObject->TicketFlagDelete( 6697 TicketID => 123, 6698 Key => 'Seen', 6699 UserID => 123, 6700 ); 6701 6702 my $Success = $TicketObject->TicketFlagDelete( 6703 TicketID => 123, 6704 Key => 'Seen', 6705 AllUsers => 1, 6706 ); 6707 6708Events: 6709 TicketFlagDelete 6710 6711=cut 6712 6713sub TicketFlagDelete { 6714 my ( $Self, %Param ) = @_; 6715 6716 # check needed stuff 6717 for my $Needed (qw(TicketID Key)) { 6718 if ( !$Param{$Needed} ) { 6719 $Kernel::OM->Get('Kernel::System::Log')->Log( 6720 Priority => 'error', 6721 Message => "Need $Needed!", 6722 ); 6723 return; 6724 } 6725 } 6726 6727 # only one of these parameters is needed 6728 if ( !$Param{UserID} && !$Param{AllUsers} ) { 6729 $Kernel::OM->Get('Kernel::System::Log')->Log( 6730 Priority => 'error', 6731 Message => "Need UserID or AllUsers param!", 6732 ); 6733 return; 6734 } 6735 6736 # if all users parameter was given 6737 if ( $Param{AllUsers} ) { 6738 6739 # get all affected users 6740 my @AllTicketFlags = $Self->TicketFlagGet( 6741 TicketID => $Param{TicketID}, 6742 AllUsers => 1, 6743 ); 6744 6745 # delete flags from database 6746 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 6747 SQL => ' 6748 DELETE FROM ticket_flag 6749 WHERE ticket_id = ? 6750 AND ticket_key = ?', 6751 Bind => [ \$Param{TicketID}, \$Param{Key} ], 6752 ); 6753 6754 # delete cache 6755 $Kernel::OM->Get('Kernel::System::Cache')->Delete( 6756 Type => $Self->{CacheType}, 6757 Key => 'TicketFlag::' . $Param{TicketID}, 6758 ); 6759 6760 for my $Record (@AllTicketFlags) { 6761 6762 $Self->EventHandler( 6763 Event => 'TicketFlagDelete', 6764 Data => { 6765 TicketID => $Param{TicketID}, 6766 Key => $Param{Key}, 6767 UserID => $Record->{UserID}, 6768 }, 6769 UserID => $Record->{UserID}, 6770 ); 6771 } 6772 } 6773 else { 6774 6775 # delete flags from database 6776 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 6777 SQL => ' 6778 DELETE FROM ticket_flag 6779 WHERE ticket_id = ? 6780 AND create_by = ? 6781 AND ticket_key = ?', 6782 Bind => [ \$Param{TicketID}, \$Param{UserID}, \$Param{Key} ], 6783 ); 6784 6785 # delete cache 6786 $Kernel::OM->Get('Kernel::System::Cache')->Delete( 6787 Type => $Self->{CacheType}, 6788 Key => 'TicketFlag::' . $Param{TicketID}, 6789 ); 6790 6791 $Self->EventHandler( 6792 Event => 'TicketFlagDelete', 6793 Data => { 6794 TicketID => $Param{TicketID}, 6795 Key => $Param{Key}, 6796 UserID => $Param{UserID}, 6797 }, 6798 UserID => $Param{UserID}, 6799 ); 6800 } 6801 6802 return 1; 6803} 6804 6805=head2 TicketFlagGet() 6806 6807get ticket flags 6808 6809 my %Flags = $TicketObject->TicketFlagGet( 6810 TicketID => 123, 6811 UserID => 123, # to get flags of one user 6812 ); 6813 6814 my @Flags = $TicketObject->TicketFlagGet( 6815 TicketID => 123, 6816 AllUsers => 1, # to get flags of all users 6817 ); 6818 6819=cut 6820 6821sub TicketFlagGet { 6822 my ( $Self, %Param ) = @_; 6823 6824 # check needed stuff 6825 if ( !$Param{TicketID} ) { 6826 $Kernel::OM->Get('Kernel::System::Log')->Log( 6827 Priority => 'error', 6828 Message => "Need TicketID!", 6829 ); 6830 return; 6831 } 6832 6833 # check optional 6834 if ( !$Param{UserID} && !$Param{AllUsers} ) { 6835 $Kernel::OM->Get('Kernel::System::Log')->Log( 6836 Priority => 'error', 6837 Message => "Need UserID or AllUsers param!", 6838 ); 6839 return; 6840 } 6841 6842 # get cache object 6843 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 6844 6845 # check cache 6846 my $Flags = $CacheObject->Get( 6847 Type => $Self->{CacheType}, 6848 Key => 'TicketFlag::' . $Param{TicketID}, 6849 ); 6850 6851 if ( !$Flags || ref $Flags ne 'HASH' ) { 6852 6853 # get database object 6854 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 6855 6856 # get all ticket flags of the given ticket 6857 return if !$DBObject->Prepare( 6858 SQL => ' 6859 SELECT create_by, ticket_key, ticket_value 6860 FROM ticket_flag 6861 WHERE ticket_id = ?', 6862 Bind => [ \$Param{TicketID} ], 6863 ); 6864 6865 # fetch the result 6866 $Flags = {}; 6867 while ( my @Row = $DBObject->FetchrowArray() ) { 6868 $Flags->{ $Row[0] }->{ $Row[1] } = $Row[2]; 6869 } 6870 6871 # set cache 6872 $CacheObject->Set( 6873 Type => $Self->{CacheType}, 6874 TTL => $Self->{CacheTTL}, 6875 Key => 'TicketFlag::' . $Param{TicketID}, 6876 Value => $Flags, 6877 ); 6878 } 6879 6880 if ( $Param{AllUsers} ) { 6881 6882 my @FlagAllUsers; 6883 for my $UserID ( sort keys %{$Flags} ) { 6884 6885 for my $Key ( sort keys %{ $Flags->{$UserID} } ) { 6886 6887 push @FlagAllUsers, { 6888 Key => $Key, 6889 Value => $Flags->{$UserID}->{$Key}, 6890 UserID => $UserID, 6891 }; 6892 } 6893 } 6894 6895 return @FlagAllUsers; 6896 } 6897 6898 # extract user tags 6899 my $UserTags = $Flags->{ $Param{UserID} } || {}; 6900 6901 return %{$UserTags}; 6902} 6903 6904=head2 TicketArticleStorageSwitch() 6905 6906move article storage from one backend to other backend 6907 6908 my $Success = $TicketObject->TicketArticleStorageSwitch( 6909 TicketID => 123, 6910 Source => 'ArticleStorageDB', 6911 Destination => 'ArticleStorageFS', 6912 UserID => 1, 6913 ); 6914 6915=cut 6916 6917sub TicketArticleStorageSwitch { 6918 my ( $Self, %Param ) = @_; 6919 6920 # check needed stuff 6921 for my $Needed (qw(TicketID Source Destination UserID)) { 6922 if ( !$Param{$Needed} ) { 6923 $Kernel::OM->Get('Kernel::System::Log')->Log( 6924 Priority => 'error', 6925 Message => "Need $Needed!" 6926 ); 6927 return; 6928 } 6929 } 6930 6931 # check source vs. destination 6932 return 1 if $Param{Source} eq $Param{Destination}; 6933 6934 # get config object 6935 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 6936 6937 # reset events and remember 6938 my $EventConfig = $ConfigObject->Get('Ticket::EventModulePost'); 6939 $ConfigObject->{'Ticket::EventModulePost'} = {}; 6940 6941 # make sure that CheckAllBackends is set for the duration of this method 6942 $Self->{CheckAllBackends} = 1; 6943 6944 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 6945 my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); 6946 6947 # Get articles. 6948 my @Articles = $ArticleObject->ArticleList( 6949 TicketID => $Param{TicketID}, 6950 UserID => $Param{UserID}, 6951 ); 6952 6953 ARTICLE: 6954 for my $Article (@Articles) { 6955 6956 # Handle only MIME based articles. 6957 my $BackendName = $ArticleObject->BackendForArticle( 6958 TicketID => $Param{TicketID}, 6959 ArticleID => $Article->{ArticleID} 6960 )->ChannelNameGet(); 6961 next ARTICLE if $BackendName !~ /^(Email|Phone|Internal)$/; 6962 6963 my $ArticleObjectSource = Kernel::System::Ticket::Article::Backend::MIMEBase->new( 6964 ArticleStorageModule => 'Kernel::System::Ticket::Article::Backend::MIMEBase::' . $Param{Source}, 6965 ); 6966 if ( 6967 !$ArticleObjectSource 6968 || $ArticleObjectSource->{ArticleStorageModule} ne 6969 "Kernel::System::Ticket::Article::Backend::MIMEBase::$Param{Source}" 6970 ) 6971 { 6972 $Kernel::OM->Get('Kernel::System::Log')->Log( 6973 Priority => 'error', 6974 Message => "Could not create Kernel::System::Ticket::Article::Backend::MIMEBase::" . $Param{Source}, 6975 ); 6976 die; 6977 } 6978 6979 # read source attachments 6980 my %Index = $ArticleObjectSource->ArticleAttachmentIndex( 6981 ArticleID => $Article->{ArticleID}, 6982 OnlyMyBackend => 1, 6983 ); 6984 6985 # read source plain 6986 my $Plain = $ArticleObjectSource->ArticlePlain( 6987 ArticleID => $Article->{ArticleID}, 6988 OnlyMyBackend => 1, 6989 ); 6990 my $PlainMD5Sum = ''; 6991 if ($Plain) { 6992 my $PlainMD5 = $Plain; 6993 $PlainMD5Sum = $MainObject->MD5sum( 6994 String => \$PlainMD5, 6995 ); 6996 } 6997 6998 # read source attachments 6999 my @Attachments; 7000 my %MD5Sums; 7001 for my $FileID ( sort keys %Index ) { 7002 my %Attachment = $ArticleObjectSource->ArticleAttachment( 7003 ArticleID => $Article->{ArticleID}, 7004 FileID => $FileID, 7005 OnlyMyBackend => 1, 7006 Force => 1, 7007 ); 7008 push @Attachments, \%Attachment; 7009 my $MD5Sum = $MainObject->MD5sum( 7010 String => $Attachment{Content}, 7011 ); 7012 $MD5Sums{$MD5Sum}++; 7013 } 7014 7015 # nothing to transfer 7016 next ARTICLE if !@Attachments && !$Plain; 7017 7018 my $ArticleObjectDestination = Kernel::System::Ticket::Article::Backend::MIMEBase->new( 7019 ArticleStorageModule => 'Kernel::System::Ticket::Article::Backend::MIMEBase::' . $Param{Destination}, 7020 ); 7021 if ( 7022 !$ArticleObjectDestination 7023 || $ArticleObjectDestination->{ArticleStorageModule} ne 7024 "Kernel::System::Ticket::Article::Backend::MIMEBase::$Param{Destination}" 7025 ) 7026 { 7027 $Kernel::OM->Get('Kernel::System::Log')->Log( 7028 Priority => 'error', 7029 Message => "Could not create Kernel::System::Ticket::" . $Param{Destination}, 7030 ); 7031 die; 7032 } 7033 7034 # read destination attachments 7035 %Index = $ArticleObjectDestination->ArticleAttachmentIndex( 7036 ArticleID => $Article->{ArticleID}, 7037 OnlyMyBackend => 1, 7038 ); 7039 7040 # read source attachments 7041 if (%Index) { 7042 $Kernel::OM->Get('Kernel::System::Log')->Log( 7043 Priority => 'error', 7044 Message => 7045 "Attachments of TicketID:$Param{TicketID}/ArticleID:$Article->{ArticleID} already in $Param{Destination}!", 7046 ); 7047 } 7048 else { 7049 7050 # write attachments to destination 7051 for my $Attachment (@Attachments) { 7052 7053 # Check UTF8 string for validity and replace any wrongly encoded characters with _ 7054 if ( 7055 utf8::is_utf8( $Attachment->{Filename} ) 7056 && !eval { Encode::is_utf8( $Attachment->{Filename}, 1 ) } 7057 ) 7058 { 7059 7060 Encode::_utf8_off( $Attachment->{Filename} ); 7061 7062 # replace invalid characters with � (U+FFFD, Unicode replacement character) 7063 # If it runs on good UTF-8 input, output should be identical to input 7064 $Attachment->{Filename} = eval { 7065 Encode::decode( 'UTF-8', $Attachment->{Filename} ); 7066 }; 7067 7068 # Replace wrong characters with "_". 7069 $Attachment->{Filename} =~ s{[\x{FFFD}]}{_}xms; 7070 } 7071 7072 $ArticleObjectDestination->ArticleWriteAttachment( 7073 %{$Attachment}, 7074 ArticleID => $Article->{ArticleID}, 7075 UserID => $Param{UserID}, 7076 ); 7077 } 7078 7079 # write destination plain 7080 if ($Plain) { 7081 $ArticleObjectDestination->ArticleWritePlain( 7082 Email => $Plain, 7083 ArticleID => $Article->{ArticleID}, 7084 UserID => $Param{UserID}, 7085 ); 7086 } 7087 7088 # verify destination attachments 7089 %Index = $ArticleObjectDestination->ArticleAttachmentIndex( 7090 ArticleID => $Article->{ArticleID}, 7091 OnlyMyBackend => 1, 7092 ); 7093 } 7094 7095 for my $FileID ( sort keys %Index ) { 7096 my %Attachment = $ArticleObjectDestination->ArticleAttachment( 7097 ArticleID => $Article->{ArticleID}, 7098 FileID => $FileID, 7099 OnlyMyBackend => 1, 7100 Force => 1, 7101 ); 7102 my $MD5Sum = $MainObject->MD5sum( 7103 String => \$Attachment{Content}, 7104 ); 7105 if ( $MD5Sums{$MD5Sum} ) { 7106 $MD5Sums{$MD5Sum}--; 7107 if ( !$MD5Sums{$MD5Sum} ) { 7108 delete $MD5Sums{$MD5Sum}; 7109 } 7110 } 7111 else { 7112 $Kernel::OM->Get('Kernel::System::Log')->Log( 7113 Priority => 'error', 7114 Message => 7115 "Corrupt file: $Attachment{Filename} (TicketID:$Param{TicketID}/ArticleID:$Article->{ArticleID})!", 7116 ); 7117 7118 # delete corrupt attachments from destination 7119 $ArticleObjectDestination->ArticleDeleteAttachment( 7120 ArticleID => $Article->{ArticleID}, 7121 UserID => 1, 7122 OnlyMyBackend => 1, 7123 ); 7124 7125 # set events 7126 $ConfigObject->{'Ticket::EventModulePost'} = $EventConfig; 7127 return; 7128 } 7129 } 7130 7131 # check if all files are moved 7132 if (%MD5Sums) { 7133 $Kernel::OM->Get('Kernel::System::Log')->Log( 7134 Priority => 'error', 7135 Message => 7136 "Not all files are moved! (TicketID:$Param{TicketID}/ArticleID:$Article->{ArticleID})!", 7137 ); 7138 7139 # delete incomplete attachments from destination 7140 $ArticleObjectDestination->ArticleDeleteAttachment( 7141 ArticleID => $Article->{ArticleID}, 7142 UserID => 1, 7143 OnlyMyBackend => 1, 7144 ); 7145 7146 # set events 7147 $ConfigObject->{'Ticket::EventModulePost'} = $EventConfig; 7148 return; 7149 } 7150 7151 # verify destination plain if exists in source backend 7152 if ($Plain) { 7153 my $PlainVerify = $ArticleObjectDestination->ArticlePlain( 7154 ArticleID => $Article->{ArticleID}, 7155 OnlyMyBackend => 1, 7156 ); 7157 my $PlainMD5SumVerify = ''; 7158 if ($PlainVerify) { 7159 $PlainMD5SumVerify = $MainObject->MD5sum( 7160 String => \$PlainVerify, 7161 ); 7162 } 7163 if ( $PlainMD5Sum ne $PlainMD5SumVerify ) { 7164 $Kernel::OM->Get('Kernel::System::Log')->Log( 7165 Priority => 'error', 7166 Message => 7167 "Corrupt plain file: ArticleID: $Article->{ArticleID} ($PlainMD5Sum/$PlainMD5SumVerify)", 7168 ); 7169 7170 # delete corrupt plain file from destination 7171 $ArticleObjectDestination->ArticleDeletePlain( 7172 ArticleID => $Article->{ArticleID}, 7173 UserID => 1, 7174 OnlyMyBackend => 1, 7175 ); 7176 7177 # set events 7178 $ConfigObject->{'Ticket::EventModulePost'} = $EventConfig; 7179 return; 7180 } 7181 } 7182 7183 $ArticleObjectSource->ArticleDeleteAttachment( 7184 ArticleID => $Article->{ArticleID}, 7185 UserID => 1, 7186 OnlyMyBackend => 1, 7187 ); 7188 7189 # remove source plain 7190 $ArticleObjectSource->ArticleDeletePlain( 7191 ArticleID => $Article->{ArticleID}, 7192 UserID => 1, 7193 OnlyMyBackend => 1, 7194 ); 7195 7196 # read source attachments 7197 %Index = $ArticleObjectSource->ArticleAttachmentIndex( 7198 ArticleID => $Article->{ArticleID}, 7199 OnlyMyBackend => 1, 7200 ); 7201 7202 # read source attachments 7203 if (%Index) { 7204 $Kernel::OM->Get('Kernel::System::Log')->Log( 7205 Priority => 'error', 7206 Message => "Attachments still in $Param{Source}!", 7207 ); 7208 return; 7209 } 7210 } 7211 7212 # set events 7213 $ConfigObject->{'Ticket::EventModulePost'} = $EventConfig; 7214 7215 # Restore previous behavior. 7216 $Self->{CheckAllBackends} = 7217 $ConfigObject->Get('Ticket::Article::Backend::MIMEBase::CheckAllStorageBackends') 7218 // 0; 7219 7220 return 1; 7221} 7222 7223# ProcessManagement functions 7224 7225=head2 TicketCheckForProcessType() 7226 7227 checks whether or not the ticket is of a process type. 7228 7229 $TicketObject->TicketCheckForProcessType( 7230 TicketID => 123, 7231 ); 7232 7233=cut 7234 7235sub TicketCheckForProcessType { 7236 my ( $Self, %Param ) = @_; 7237 7238 # check needed stuff 7239 if ( !$Param{TicketID} ) { 7240 $Kernel::OM->Get('Kernel::System::Log')->Log( 7241 Priority => 'error', 7242 Message => 'Need TicketID!', 7243 ); 7244 return; 7245 } 7246 7247 my $DynamicFieldName = $Kernel::OM->Get('Kernel::Config')->Get('Process::DynamicFieldProcessManagementProcessID'); 7248 7249 return if !$DynamicFieldName; 7250 $DynamicFieldName = 'DynamicField_' . $DynamicFieldName; 7251 7252 # get ticket attributes 7253 my %Ticket = $Self->TicketGet( 7254 TicketID => $Param{TicketID}, 7255 DynamicFields => 1, 7256 ); 7257 7258 # return 1 if we got process ticket 7259 return 1 if $Ticket{$DynamicFieldName}; 7260 7261 return; 7262} 7263 7264=head2 TicketCalendarGet() 7265 7266checks calendar to be used for ticket based on sla and queue 7267 7268 my $Calendar = $TicketObject->TicketCalendarGet( 7269 QueueID => 1, 7270 SLAID => 1, # optional 7271 ); 7272 7273returns calendar number or empty string for default calendar 7274 7275=cut 7276 7277sub TicketCalendarGet { 7278 my ( $Self, %Param ) = @_; 7279 7280 # check needed stuff 7281 if ( !$Param{QueueID} ) { 7282 $Kernel::OM->Get('Kernel::System::Log')->Log( 7283 Priority => 'error', 7284 Message => 'Need QueueID!' 7285 ); 7286 return; 7287 } 7288 7289 # check if SLAID was passed and if sla has a specific calendar 7290 if ( $Param{SLAID} ) { 7291 7292 my %SLAData = $Kernel::OM->Get('Kernel::System::SLA')->SLAGet( 7293 SLAID => $Param{SLAID}, 7294 UserID => 1, 7295 ); 7296 7297 # if SLA has a defined calendar, return it 7298 return $SLAData{Calendar} if $SLAData{Calendar}; 7299 } 7300 7301 # if no calendar was determined by SLA, check if queue has a specific calendar 7302 my %QueueData = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( 7303 ID => $Param{QueueID}, 7304 ); 7305 7306 # if queue has a defined calendar, return it 7307 return $QueueData{Calendar} if $QueueData{Calendar}; 7308 7309 # use default calendar 7310 return ''; 7311} 7312 7313=head2 SearchUnknownTicketCustomers() 7314 7315search customer users that are not saved in any backend 7316 7317 my $UnknownTicketCustomerList = $TicketObject->SearchUnknownTicketCustomers( 7318 SearchTerm => 'SomeSearchTerm', 7319 ); 7320 7321Returns: 7322 7323 %UnknownTicketCustomerList = ( 7324 { 7325 CustomerID => 'SomeCustomerID', 7326 CustomerUser => 'SomeCustomerUser', 7327 }, 7328 { 7329 CustomerID => 'SomeCustomerID', 7330 CustomerUser => 'SomeCustomerUser', 7331 }, 7332 ); 7333 7334=cut 7335 7336sub SearchUnknownTicketCustomers { 7337 my ( $Self, %Param ) = @_; 7338 7339 my $SearchTerm = $Param{SearchTerm} || ''; 7340 7341 # get database object 7342 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 7343 my $LikeEscapeString = $DBObject->GetDatabaseFunction('LikeEscapeString'); 7344 my $QuotedSearch = '%' . $DBObject->Quote( $SearchTerm, 'Like' ) . '%'; 7345 7346 # db query 7347 return if !$DBObject->Prepare( 7348 SQL => 7349 "SELECT DISTINCT customer_user_id, customer_id FROM ticket WHERE customer_user_id LIKE ? $LikeEscapeString", 7350 Bind => [ \$QuotedSearch ], 7351 ); 7352 my $UnknownTicketCustomerList; 7353 7354 CUSTOMERUSER: 7355 while ( my @Row = $DBObject->FetchrowArray() ) { 7356 $UnknownTicketCustomerList->{ $Row[0] } = $Row[1]; 7357 } 7358 7359 return $UnknownTicketCustomerList; 7360} 7361 7362sub TicketAcceleratorUpdate { 7363 my ( $Self, %Param ) = @_; 7364 7365 my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule') 7366 || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB'; 7367 7368 return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorUpdate(%Param); 7369} 7370 7371sub TicketAcceleratorDelete { 7372 my ( $Self, %Param ) = @_; 7373 7374 my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule') 7375 || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB'; 7376 7377 return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorDelete(%Param); 7378} 7379 7380sub TicketAcceleratorAdd { 7381 my ( $Self, %Param ) = @_; 7382 7383 my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule') 7384 || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB'; 7385 7386 return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorAdd(%Param); 7387} 7388 7389sub TicketAcceleratorIndex { 7390 my ( $Self, %Param ) = @_; 7391 7392 my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule') 7393 || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB'; 7394 7395 return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorIndex(%Param); 7396} 7397 7398sub TicketAcceleratorRebuild { 7399 my ( $Self, %Param ) = @_; 7400 7401 my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule') 7402 || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB'; 7403 7404 return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorRebuild(%Param); 7405} 7406 7407sub DESTROY { 7408 my $Self = shift; 7409 7410 # execute all transaction events 7411 $Self->EventHandlerTransaction(); 7412 7413 return 1; 7414} 7415 7416# COMPAT: to OTRS 1.x and 2.x (can be removed later) 7417 7418sub CustomerPermission { 7419 my ( $Self, %Param ) = @_; 7420 7421 return $Self->TicketCustomerPermission(%Param); 7422} 7423 7424sub InvolvedAgents { 7425 my ( $Self, %Param ) = @_; 7426 7427 return $Self->TicketInvolvedAgentsList(%Param); 7428} 7429 7430sub LockIsTicketLocked { 7431 my ( $Self, %Param ) = @_; 7432 7433 return $Self->TicketLockGet(%Param); 7434} 7435 7436sub LockSet { 7437 my ( $Self, %Param ) = @_; 7438 7439 return $Self->TicketLockSet(%Param); 7440} 7441 7442sub MoveList { 7443 my ( $Self, %Param ) = @_; 7444 7445 return $Self->TicketMoveList(%Param); 7446} 7447 7448sub MoveTicket { 7449 my ( $Self, %Param ) = @_; 7450 7451 return $Self->TicketQueueSet(%Param); 7452} 7453 7454sub MoveQueueList { 7455 my ( $Self, %Param ) = @_; 7456 7457 return $Self->TicketMoveQueueList(%Param); 7458} 7459 7460sub OwnerList { 7461 my ( $Self, %Param ) = @_; 7462 7463 return $Self->TicketOwnerList(%Param); 7464} 7465 7466sub OwnerSet { 7467 my ( $Self, %Param ) = @_; 7468 7469 return $Self->TicketOwnerSet(%Param); 7470} 7471 7472sub Permission { 7473 my ( $Self, %Param ) = @_; 7474 7475 return $Self->TicketPermission(%Param); 7476} 7477 7478sub PriorityList { 7479 my ( $Self, %Param ) = @_; 7480 7481 return $Self->TicketPriorityList(%Param); 7482} 7483 7484sub PrioritySet { 7485 my ( $Self, %Param ) = @_; 7486 7487 return $Self->TicketPrioritySet(%Param); 7488} 7489 7490sub ResponsibleList { 7491 my ( $Self, %Param ) = @_; 7492 7493 return $Self->TicketResponsibleList(%Param); 7494} 7495 7496sub ResponsibleSet { 7497 my ( $Self, %Param ) = @_; 7498 7499 return $Self->TicketResponsibleSet(%Param); 7500} 7501 7502sub SetCustomerData { 7503 my ( $Self, %Param ) = @_; 7504 7505 return $Self->TicketCustomerSet(%Param); 7506} 7507 7508sub StateList { 7509 my ( $Self, %Param ) = @_; 7510 7511 return $Self->TicketStateList(%Param); 7512} 7513 7514sub StateSet { 7515 my ( $Self, %Param ) = @_; 7516 7517 return $Self->TicketStateSet(%Param); 7518} 7519 7520=head1 PRIVATE FUNCTIONS 7521 7522=head2 _TicketCacheClear() 7523 7524Remove all caches related to specified ticket. 7525 7526 my $Success = $TicketObject->_TicketCacheClear( 7527 TicketID => 123, 7528 ); 7529 7530=cut 7531 7532sub _TicketCacheClear { 7533 my ( $Self, %Param ) = @_; 7534 7535 for my $Needed (qw(TicketID)) { 7536 if ( !defined $Param{$Needed} ) { 7537 $Kernel::OM->Get('Kernel::System::Log')->Log( 7538 Priority => 'error', 7539 Message => "Need $Needed!" 7540 ); 7541 return; 7542 } 7543 } 7544 7545 # get cache object 7546 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 7547 7548 # TicketGet() 7549 my $CacheKey = 'Cache::GetTicket' . $Param{TicketID}; 7550 $CacheObject->Delete( 7551 Type => $Self->{CacheType}, 7552 Key => $CacheKey, 7553 ); 7554 7555 # delete extended cache for TicketGet() 7556 for my $Extended ( 0 .. 1 ) { 7557 for my $FetchDynamicFields ( 0 .. 1 ) { 7558 my $CacheKeyDynamicFields = $CacheKey . '::' . $Extended . '::' . $FetchDynamicFields; 7559 7560 $CacheObject->Delete( 7561 Type => $Self->{CacheType}, 7562 Key => $CacheKeyDynamicFields, 7563 ); 7564 } 7565 } 7566 7567 $Kernel::OM->Get('Kernel::System::Ticket::Article')->_ArticleCacheClear(%Param); 7568 7569 return 1; 7570} 7571 7572=head2 _TicketGetExtended() 7573 7574Collect extended attributes for given ticket, 7575namely first response, first lock and close data. 7576 7577 my %TicketExtended = $TicketObject->_TicketGetExtended( 7578 TicketID => $Param{TicketID}, 7579 Ticket => \%Ticket, 7580 ); 7581 7582=cut 7583 7584sub _TicketGetExtended { 7585 my ( $Self, %Param ) = @_; 7586 7587 # check needed stuff 7588 for my $Needed (qw(TicketID Ticket)) { 7589 if ( !defined $Param{$Needed} ) { 7590 $Kernel::OM->Get('Kernel::System::Log')->Log( 7591 Priority => 'error', 7592 Message => "Need $Needed!" 7593 ); 7594 return; 7595 } 7596 } 7597 7598 # get extended attributes 7599 my %FirstResponse = $Self->_TicketGetFirstResponse(%Param); 7600 my %FirstLock = $Self->_TicketGetFirstLock(%Param); 7601 my %TicketGetClosed = $Self->_TicketGetClosed(%Param); 7602 7603 # return all as hash 7604 return ( %TicketGetClosed, %FirstResponse, %FirstLock ); 7605} 7606 7607=head2 _TicketGetFirstResponse() 7608 7609Collect attributes of first response for given ticket. 7610 7611 my %FirstResponse = $TicketObject->_TicketGetFirstResponse( 7612 TicketID => $Param{TicketID}, 7613 Ticket => \%Ticket, 7614 ); 7615 7616=cut 7617 7618sub _TicketGetFirstResponse { 7619 my ( $Self, %Param ) = @_; 7620 7621 # check needed stuff 7622 for my $Needed (qw(TicketID Ticket)) { 7623 if ( !defined $Param{$Needed} ) { 7624 $Kernel::OM->Get('Kernel::System::Log')->Log( 7625 Priority => 'error', 7626 Message => "Need $Needed!" 7627 ); 7628 return; 7629 } 7630 } 7631 7632 # get database object 7633 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 7634 7635 # check if first response is already done 7636 return if !$DBObject->Prepare( 7637 SQL => ' 7638 SELECT a.create_time,a.id FROM article a, article_sender_type ast 7639 WHERE a.article_sender_type_id = ast.id 7640 AND a.ticket_id = ? 7641 AND ast.name = ? 7642 AND a.is_visible_for_customer = ? 7643 ORDER BY a.create_time', 7644 Bind => [ \$Param{TicketID}, \'agent', \1 ], 7645 Limit => 1, 7646 ); 7647 7648 my %Data; 7649 while ( my @Row = $DBObject->FetchrowArray() ) { 7650 $Data{FirstResponse} = $Row[0]; 7651 7652 # cleanup time stamps (some databases are using e. g. 2008-02-25 22:03:00.000000 7653 # and 0000-00-00 00:00:00 time stamps) 7654 $Data{FirstResponse} =~ s/^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d)\..+?$/$1/; 7655 } 7656 7657 return if !$Data{FirstResponse}; 7658 7659 # get escalation properties 7660 my %Escalation = $Self->TicketEscalationPreferences( 7661 Ticket => $Param{Ticket}, 7662 UserID => $Param{UserID} || 1, 7663 ); 7664 7665 if ( $Escalation{FirstResponseTime} ) { 7666 7667 # create datetime object 7668 my $DateTimeObject = $Kernel::OM->Create( 7669 'Kernel::System::DateTime', 7670 ObjectParams => { 7671 String => $Param{Ticket}->{Created}, 7672 } 7673 ); 7674 7675 my $FirstResponseTimeObj = $DateTimeObject->Clone(); 7676 $FirstResponseTimeObj->Set( 7677 String => $Data{FirstResponse} 7678 ); 7679 7680 my $DeltaObj = $DateTimeObject->Delta( 7681 DateTimeObject => $FirstResponseTimeObj, 7682 ForWorkingTime => 1, 7683 Calendar => $Escalation{Calendar}, 7684 ); 7685 7686 my $WorkingTime = $DeltaObj ? $DeltaObj->{AbsoluteSeconds} : 0; 7687 7688 $Data{FirstResponseInMin} = int( $WorkingTime / 60 ); 7689 my $EscalationFirstResponseTime = $Escalation{FirstResponseTime} * 60; 7690 $Data{FirstResponseDiffInMin} = 7691 int( ( $EscalationFirstResponseTime - $WorkingTime ) / 60 ); 7692 } 7693 7694 return %Data; 7695} 7696 7697=head2 _TicketGetClosed() 7698 7699Collect attributes of (last) closing for given ticket. 7700 7701 my %TicketGetClosed = $TicketObject->_TicketGetClosed( 7702 TicketID => $Param{TicketID}, 7703 Ticket => \%Ticket, 7704 ); 7705 7706=cut 7707 7708sub _TicketGetClosed { 7709 my ( $Self, %Param ) = @_; 7710 7711 # check needed stuff 7712 for my $Needed (qw(TicketID Ticket)) { 7713 if ( !defined $Param{$Needed} ) { 7714 $Kernel::OM->Get('Kernel::System::Log')->Log( 7715 Priority => 'error', 7716 Message => "Need $Needed!" 7717 ); 7718 return; 7719 } 7720 } 7721 7722 # get close state types 7723 my @List = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType( 7724 StateType => ['closed'], 7725 Result => 'ID', 7726 ); 7727 return if !@List; 7728 7729 # Get id for history types 7730 my @HistoryTypeIDs; 7731 for my $HistoryType (qw(StateUpdate NewTicket)) { 7732 push @HistoryTypeIDs, $Self->HistoryTypeLookup( Type => $HistoryType ); 7733 } 7734 7735 # get database object 7736 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 7737 7738 return if !$DBObject->Prepare( 7739 SQL => " 7740 SELECT MAX(create_time) 7741 FROM ticket_history 7742 WHERE ticket_id = ? 7743 AND state_id IN (${\(join ', ', sort @List)}) 7744 AND history_type_id IN (${\(join ', ', sort @HistoryTypeIDs)}) 7745 ", 7746 Bind => [ \$Param{TicketID} ], 7747 ); 7748 7749 my %Data; 7750 ROW: 7751 while ( my @Row = $DBObject->FetchrowArray() ) { 7752 last ROW if !defined $Row[0]; 7753 $Data{Closed} = $Row[0]; 7754 7755 # cleanup time stamps (some databases are using e. g. 2008-02-25 22:03:00.000000 7756 # and 0000-00-00 00:00:00 time stamps) 7757 $Data{Closed} =~ s/^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d)\..+?$/$1/; 7758 } 7759 7760 return if !$Data{Closed}; 7761 7762 # get escalation properties 7763 my %Escalation = $Self->TicketEscalationPreferences( 7764 Ticket => $Param{Ticket}, 7765 UserID => $Param{UserID} || 1, 7766 ); 7767 7768 # create datetime object 7769 my $DateTimeObject = $Kernel::OM->Create( 7770 'Kernel::System::DateTime', 7771 ObjectParams => { 7772 String => $Param{Ticket}->{Created}, 7773 } 7774 ); 7775 7776 my $SolutionTimeObj = $Kernel::OM->Create( 7777 'Kernel::System::DateTime', 7778 ObjectParams => { 7779 String => $Data{Closed}, 7780 } 7781 ); 7782 7783 my $DeltaObj = $DateTimeObject->Delta( 7784 DateTimeObject => $SolutionTimeObj, 7785 ForWorkingTime => 1, 7786 Calendar => $Escalation{Calendar}, 7787 ); 7788 7789 my $WorkingTime = $DeltaObj ? $DeltaObj->{AbsoluteSeconds} : 0; 7790 7791 $Data{SolutionInMin} = int( $WorkingTime / 60 ); 7792 7793 if ( $Escalation{SolutionTime} ) { 7794 my $EscalationSolutionTime = $Escalation{SolutionTime} * 60; 7795 $Data{SolutionDiffInMin} = 7796 int( ( $EscalationSolutionTime - $WorkingTime ) / 60 ); 7797 } 7798 7799 return %Data; 7800} 7801 7802=head2 _TicketGetFirstLock() 7803 7804Collect first lock time for given ticket. 7805 7806 my %FirstLock = $TicketObject->_TicketGetFirstLock( 7807 TicketID => $Param{TicketID}, 7808 Ticket => \%Ticket, 7809 ); 7810 7811=cut 7812 7813sub _TicketGetFirstLock { 7814 my ( $Self, %Param ) = @_; 7815 7816 # check needed stuff 7817 for my $Needed (qw(TicketID Ticket)) { 7818 if ( !defined $Param{$Needed} ) { 7819 $Kernel::OM->Get('Kernel::System::Log')->Log( 7820 Priority => 'error', 7821 Message => "Need $Needed!" 7822 ); 7823 return; 7824 } 7825 } 7826 7827 my $LockHistoryTypeID = $Self->HistoryTypeLookup( Type => 'Lock' ); 7828 7829 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 7830 7831 # get first lock 7832 return if !$DBObject->Prepare( 7833 SQL => 'SELECT create_time ' 7834 . 'FROM ticket_history ' 7835 . 'WHERE ticket_id = ? AND history_type_id = ? ' 7836 . 'ORDER BY create_time ASC, id ASC', 7837 Bind => [ \$Param{TicketID}, \$LockHistoryTypeID, ], 7838 Limit => 1, 7839 ); 7840 7841 my %Data; 7842 while ( my @Row = $DBObject->FetchrowArray() ) { 7843 $Data{FirstLock} = $Row[0]; 7844 7845 # cleanup time stamp (some databases are using e. g. '2008-02-25 22:03:00.000000' time stamps) 7846 $Data{FirstLock} =~ s{ \A ( \d{4} - \d{2} - \d{2} [ ] \d{2} : \d{2} : \d{2} ) .* \z }{$1}xms; 7847 } 7848 7849 return %Data; 7850} 7851 78521; 7853 7854=head1 TERMS AND CONDITIONS 7855 7856This software is part of the OTRS project (L<https://otrs.org/>). 7857 7858This software comes with ABSOLUTELY NO WARRANTY. For details, see 7859the enclosed file COPYING for license information (GPL). If you 7860did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 7861 7862=cut 7863