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::LinkObject; 10 11use strict; 12use warnings; 13 14use parent qw( Kernel::System::EventHandler ); 15 16our @ObjectDependencies = ( 17 'Kernel::Config', 18 'Kernel::System::Cache', 19 'Kernel::System::CheckItem', 20 'Kernel::System::DateTime', 21 'Kernel::System::DB', 22 'Kernel::System::Log', 23 'Kernel::System::Main', 24 'Kernel::System::Valid', 25); 26 27=head1 NAME 28 29Kernel::System::LinkObject - to link objects like tickets, faq entries, ... 30 31=head1 DESCRIPTION 32 33All functions to link objects like tickets, faq entries, ... 34 35=head1 PUBLIC INTERFACE 36 37=head2 new() 38 39Don't use the constructor directly, use the ObjectManager instead: 40 41 my $LinkObject = $Kernel::OM->Get('Kernel::System::LinkObject'); 42 43=cut 44 45sub new { 46 my ( $Type, %Param ) = @_; 47 48 my $Self = {}; 49 bless( $Self, $Type ); 50 51 # Initialize event handler. 52 $Self->EventHandlerInit( 53 Config => 'LinkObject::EventModulePost', 54 ); 55 56 $Self->{CacheType} = 'LinkObject'; 57 $Self->{CacheTTL} = 60 * 60 * 24 * 20; 58 59 return $Self; 60} 61 62=head2 PossibleTypesList() 63 64return a hash of all possible types 65 66Return 67 %PossibleTypesList = ( 68 'Normal' => 1, 69 'ParentChild' => 1, 70 ); 71 72 my %PossibleTypesList = $LinkObject->PossibleTypesList( 73 Object1 => 'Ticket', 74 Object2 => 'FAQ', 75 ); 76 77=cut 78 79sub PossibleTypesList { 80 my ( $Self, %Param ) = @_; 81 82 # check needed stuff 83 for my $Argument (qw(Object1 Object2)) { 84 if ( !$Param{$Argument} ) { 85 $Kernel::OM->Get('Kernel::System::Log')->Log( 86 Priority => 'error', 87 Message => "Need $Argument!", 88 ); 89 return; 90 } 91 } 92 93 # get possible link list 94 my %PossibleLinkList = $Self->PossibleLinkList(); 95 96 # remove not needed entries 97 POSSIBLELINK: 98 for my $PossibleLink ( sort keys %PossibleLinkList ) { 99 100 # extract objects 101 my $Object1 = $PossibleLinkList{$PossibleLink}->{Object1}; 102 my $Object2 = $PossibleLinkList{$PossibleLink}->{Object2}; 103 104 next POSSIBLELINK 105 if ( $Object1 eq $Param{Object1} && $Object2 eq $Param{Object2} ) 106 || ( $Object2 eq $Param{Object1} && $Object1 eq $Param{Object2} ); 107 108 # remove entry from list if objects don't match 109 delete $PossibleLinkList{$PossibleLink}; 110 } 111 112 # get type list 113 my %TypeList = $Self->TypeList(); 114 115 # check types 116 POSSIBLELINK: 117 for my $PossibleLink ( sort keys %PossibleLinkList ) { 118 119 # extract type 120 my $Type = $PossibleLinkList{$PossibleLink}->{Type} || ''; 121 122 next POSSIBLELINK if $TypeList{$Type}; 123 124 # remove entry from list if type doesn't exist 125 delete $PossibleLinkList{$PossibleLink}; 126 } 127 128 # extract the type list 129 my %PossibleTypesList; 130 for my $PossibleLink ( sort keys %PossibleLinkList ) { 131 132 # extract type 133 my $Type = $PossibleLinkList{$PossibleLink}->{Type}; 134 135 $PossibleTypesList{$Type} = 1; 136 } 137 138 return %PossibleTypesList; 139} 140 141=head2 PossibleObjectsList() 142 143return a hash of all possible objects 144 145Return 146 %PossibleObjectsList = ( 147 'Ticket' => 1, 148 'FAQ' => 1, 149 ); 150 151 my %PossibleObjectsList = $LinkObject->PossibleObjectsList( 152 Object => 'Ticket', 153 ); 154 155=cut 156 157sub PossibleObjectsList { 158 my ( $Self, %Param ) = @_; 159 160 # check needed stuff 161 for my $Argument (qw(Object)) { 162 if ( !$Param{$Argument} ) { 163 $Kernel::OM->Get('Kernel::System::Log')->Log( 164 Priority => 'error', 165 Message => "Need $Argument!", 166 ); 167 return; 168 } 169 } 170 171 # get possible link list 172 my %PossibleLinkList = $Self->PossibleLinkList(); 173 174 # investigate the possible object list 175 my %PossibleObjectsList; 176 POSSIBLELINK: 177 for my $PossibleLink ( sort keys %PossibleLinkList ) { 178 179 # extract objects 180 my $Object1 = $PossibleLinkList{$PossibleLink}->{Object1}; 181 my $Object2 = $PossibleLinkList{$PossibleLink}->{Object2}; 182 183 next POSSIBLELINK if $Param{Object} ne $Object1 && $Param{Object} ne $Object2; 184 185 # add object to list 186 if ( $Param{Object} eq $Object1 ) { 187 $PossibleObjectsList{$Object2} = 1; 188 } 189 else { 190 $PossibleObjectsList{$Object1} = 1; 191 } 192 } 193 194 return %PossibleObjectsList; 195} 196 197=head2 PossibleLinkList() 198 199return a 2 dimensional hash list of all possible links 200 201Return 202 %PossibleLinkList = ( 203 001 => { 204 Object1 => 'Ticket', 205 Object2 => 'Ticket', 206 Type => 'Normal', 207 }, 208 002 => { 209 Object1 => 'Ticket', 210 Object2 => 'Ticket', 211 Type => 'ParentChild', 212 }, 213 ); 214 215 my %PossibleLinkList = $LinkObject->PossibleLinkList(); 216 217=cut 218 219sub PossibleLinkList { 220 my ( $Self, %Param ) = @_; 221 222 # get needed objects 223 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 224 my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); 225 226 # get possible link list 227 my $PossibleLinkListRef = $ConfigObject->Get('LinkObject::PossibleLink') || {}; 228 my %PossibleLinkList = %{$PossibleLinkListRef}; 229 230 # prepare the possible link list 231 POSSIBLELINK: 232 for my $PossibleLink ( sort keys %PossibleLinkList ) { 233 234 # check the object1, object2 and type string 235 ARGUMENT: 236 for my $Argument (qw(Object1 Object2 Type)) { 237 238 # set empty string as default value 239 $PossibleLinkList{$PossibleLink}->{$Argument} ||= ''; 240 241 # trim the argument 242 $CheckItemObject->StringClean( 243 StringRef => \$PossibleLinkList{$PossibleLink}->{$Argument}, 244 ); 245 246 # extract value 247 my $Value = $PossibleLinkList{$PossibleLink}->{$Argument} || ''; 248 249 next ARGUMENT if $Value && $Value !~ m{ :: }xms && $Value !~ m{ \s }xms; 250 251 # log the error 252 $Kernel::OM->Get('Kernel::System::Log')->Log( 253 Priority => 'error', 254 Message => 255 "The $Argument '$Value' is invalid in SysConfig (LinkObject::PossibleLink)!", 256 ); 257 258 # remove entry from list if it is invalid 259 delete $PossibleLinkList{$PossibleLink}; 260 261 next POSSIBLELINK; 262 } 263 } 264 265 # get location of the backend modules 266 my $BackendLocation = $ConfigObject->Get('Home') . '/Kernel/System/LinkObject/'; 267 268 # check the existing objects 269 POSSIBLELINK: 270 for my $PossibleLink ( sort keys %PossibleLinkList ) { 271 272 # check if object backends exist 273 ARGUMENT: 274 for my $Argument (qw(Object1 Object2)) { 275 276 # extract object 277 my $Object = $PossibleLinkList{$PossibleLink}->{$Argument}; 278 279 next ARGUMENT if -e $BackendLocation . $Object . '.pm'; 280 281 # remove entry from list if it is invalid 282 delete $PossibleLinkList{$PossibleLink}; 283 284 next POSSIBLELINK; 285 } 286 } 287 288 # get type list 289 my %TypeList = $Self->TypeList(); 290 291 # check types 292 POSSIBLELINK: 293 for my $PossibleLink ( sort keys %PossibleLinkList ) { 294 295 # extract type 296 my $Type = $PossibleLinkList{$PossibleLink}->{Type}; 297 298 next POSSIBLELINK if $TypeList{$Type}; 299 300 # log the error 301 $Kernel::OM->Get('Kernel::System::Log')->Log( 302 Priority => 'error', 303 Message => "The LinkType '$Type' is invalid in SysConfig (LinkObject::PossibleLink)!", 304 ); 305 306 # remove entry from list if type doesn't exist 307 delete $PossibleLinkList{$PossibleLink}; 308 } 309 310 return %PossibleLinkList; 311} 312 313=head2 LinkAdd() 314 315add a new link between two elements 316 317 $True = $LinkObject->LinkAdd( 318 SourceObject => 'Ticket', 319 SourceKey => '321', 320 TargetObject => 'FAQ', 321 TargetKey => '5', 322 Type => 'ParentChild', 323 State => 'Valid', 324 UserID => 1, 325 ); 326 327=cut 328 329sub LinkAdd { 330 my ( $Self, %Param ) = @_; 331 332 # check needed stuff 333 for my $Argument (qw(SourceObject SourceKey TargetObject TargetKey Type State UserID)) { 334 if ( !$Param{$Argument} ) { 335 $Kernel::OM->Get('Kernel::System::Log')->Log( 336 Priority => 'error', 337 Message => "Need $Argument!", 338 ); 339 return; 340 } 341 } 342 343 # check if source and target are the same object 344 if ( $Param{SourceObject} eq $Param{TargetObject} && $Param{SourceKey} eq $Param{TargetKey} ) { 345 $Kernel::OM->Get('Kernel::System::Log')->Log( 346 Priority => 'error', 347 Message => 'Impossible to link object with itself!', 348 ); 349 return; 350 } 351 352 # lookup the object ids 353 OBJECT: 354 for my $Object (qw(SourceObject TargetObject)) { 355 356 # lookup the object id 357 $Param{ $Object . 'ID' } = $Self->ObjectLookup( 358 Name => $Param{$Object}, 359 ); 360 361 next OBJECT if $Param{ $Object . 'ID' }; 362 363 $Kernel::OM->Get('Kernel::System::Log')->Log( 364 Priority => 'error', 365 Message => "Invalid $Object is given!", 366 ); 367 368 return; 369 } 370 371 # get a list of possible link types for the two objects 372 my %PossibleTypesList = $Self->PossibleTypesList( 373 Object1 => $Param{SourceObject}, 374 Object2 => $Param{TargetObject}, 375 ); 376 377 # check if wanted link type is possible 378 if ( !$PossibleTypesList{ $Param{Type} } ) { 379 $Kernel::OM->Get('Kernel::System::Log')->Log( 380 Priority => 'error', 381 Message => 382 "Not possible to create a '$Param{Type}' link between $Param{SourceObject} and $Param{TargetObject}!", 383 ); 384 return; 385 } 386 387 # lookup state id 388 my $StateID = $Self->StateLookup( 389 Name => $Param{State}, 390 ); 391 392 # lookup type id 393 my $TypeID = $Self->TypeLookup( 394 Name => $Param{Type}, 395 UserID => $Param{UserID}, 396 ); 397 398 # get database object 399 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 400 401 # check if link already exists in database 402 return if !$DBObject->Prepare( 403 SQL => ' 404 SELECT source_object_id, source_key, state_id 405 FROM link_relation 406 WHERE ( 407 ( source_object_id = ? AND source_key = ? 408 AND target_object_id = ? AND target_key = ? ) 409 OR 410 ( source_object_id = ? AND source_key = ? 411 AND target_object_id = ? AND target_key = ? ) 412 ) 413 AND type_id = ?', 414 Bind => [ 415 \$Param{SourceObjectID}, \$Param{SourceKey}, 416 \$Param{TargetObjectID}, \$Param{TargetKey}, 417 \$Param{TargetObjectID}, \$Param{TargetKey}, 418 \$Param{SourceObjectID}, \$Param{SourceKey}, 419 \$TypeID, 420 ], 421 Limit => 1, 422 ); 423 424 # fetch the result 425 my %Existing; 426 while ( my @Row = $DBObject->FetchrowArray() ) { 427 $Existing{SourceObjectID} = $Row[0]; 428 $Existing{SourceKey} = $Row[1]; 429 $Existing{StateID} = $Row[2]; 430 } 431 432 # link exists already 433 if (%Existing) { 434 435 # existing link has a different StateID than the new link 436 if ( $Existing{StateID} ne $StateID ) { 437 438 $Kernel::OM->Get('Kernel::System::Log')->Log( 439 Priority => 'error', 440 Message => "Link already exists between these two objects " 441 . "with a different state id '$Existing{StateID}'!", 442 ); 443 return; 444 } 445 446 # get type data 447 my %TypeData = $Self->TypeGet( 448 TypeID => $TypeID, 449 ); 450 451 return 1 if !$TypeData{Pointed}; 452 return 1 if $Existing{SourceObjectID} eq $Param{SourceObjectID} 453 && $Existing{SourceKey} eq $Param{SourceKey}; 454 455 # log error 456 $Kernel::OM->Get('Kernel::System::Log')->Log( 457 Priority => 'error', 458 Message => 'Link already exists between these two objects in opposite direction!', 459 ); 460 return; 461 } 462 463 # get all links that the source object already has 464 my $Links = $Self->LinkList( 465 Object => $Param{SourceObject}, 466 Key => $Param{SourceKey}, 467 State => $Param{State}, 468 UserID => $Param{UserID}, 469 ); 470 471 # check type groups 472 OBJECT: 473 for my $Object ( sort keys %{$Links} ) { 474 475 next OBJECT if $Object ne $Param{TargetObject}; 476 477 TYPE: 478 for my $Type ( sort keys %{ $Links->{$Object} } ) { 479 480 # extract source and target 481 my $Source = $Links->{$Object}->{$Type}->{Source} ||= {}; 482 my $Target = $Links->{$Object}->{$Type}->{Target} ||= {}; 483 484 # check if source and target object are already linked 485 next TYPE if !$Source->{ $Param{TargetKey} } && !$Target->{ $Param{TargetKey} }; 486 487 # check the type groups 488 my $TypeGroupCheck = $Self->PossibleType( 489 Type1 => $Type, 490 Type2 => $Param{Type}, 491 ); 492 493 next TYPE if $TypeGroupCheck; 494 495 # existing link type is in a type group with the new link 496 $Kernel::OM->Get('Kernel::System::Log')->Log( 497 Priority => 'error', 498 Message => 'Another Link already exists within the same type group!', 499 ); 500 501 return; 502 } 503 } 504 505 # get backend of source object 506 my $BackendSourceObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{SourceObject} ); 507 508 return if !$BackendSourceObject; 509 510 # get backend of target object 511 my $BackendTargetObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{TargetObject} ); 512 513 return if !$BackendTargetObject; 514 515 # run pre event module of source object 516 $BackendSourceObject->LinkAddPre( 517 Key => $Param{SourceKey}, 518 TargetObject => $Param{TargetObject}, 519 TargetKey => $Param{TargetKey}, 520 Type => $Param{Type}, 521 State => $Param{State}, 522 UserID => $Param{UserID}, 523 ); 524 525 # run pre event module of target object 526 $BackendTargetObject->LinkAddPre( 527 Key => $Param{TargetKey}, 528 SourceObject => $Param{SourceObject}, 529 SourceKey => $Param{SourceKey}, 530 Type => $Param{Type}, 531 State => $Param{State}, 532 UserID => $Param{UserID}, 533 ); 534 535 return if !$DBObject->Do( 536 SQL => ' 537 INSERT INTO link_relation 538 (source_object_id, source_key, target_object_id, target_key, 539 type_id, state_id, create_time, create_by) 540 VALUES (?, ?, ?, ?, ?, ?, current_timestamp, ?)', 541 Bind => [ 542 \$Param{SourceObjectID}, \$Param{SourceKey}, 543 \$Param{TargetObjectID}, \$Param{TargetKey}, 544 \$TypeID, \$StateID, \$Param{UserID}, 545 ], 546 ); 547 548 # delete affected caches (both directions) 549 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 550 for my $Direction (qw(Source Target)) { 551 my $CacheKey = 552 'Cache::LinkListRaw' 553 . '::Direction' . $Direction 554 . '::ObjectID' . $Param{ $Direction . 'ObjectID' } 555 . '::StateID' . $StateID; 556 $CacheObject->Delete( 557 Type => $Self->{CacheType}, 558 Key => $CacheKey, 559 ); 560 } 561 562 # run post event module of source object 563 $BackendSourceObject->LinkAddPost( 564 Key => $Param{SourceKey}, 565 TargetObject => $Param{TargetObject}, 566 TargetKey => $Param{TargetKey}, 567 Type => $Param{Type}, 568 State => $Param{State}, 569 UserID => $Param{UserID}, 570 ); 571 572 # run post event module of target object 573 $BackendTargetObject->LinkAddPost( 574 Key => $Param{TargetKey}, 575 SourceObject => $Param{SourceObject}, 576 SourceKey => $Param{SourceKey}, 577 Type => $Param{Type}, 578 State => $Param{State}, 579 UserID => $Param{UserID}, 580 ); 581 582 # Run event handlers. 583 $Self->EventHandler( 584 Event => 'LinkObjectLinkAdd', 585 Data => { 586 SourceObject => $Param{SourceObject}, 587 SourceKey => $Param{SourceKey}, 588 TargetObject => $Param{TargetObject}, 589 TargetKey => $Param{TargetKey}, 590 Type => $Param{Type}, 591 State => $Param{State}, 592 }, 593 UserID => $Param{UserID}, 594 ); 595 596 return 1; 597} 598 599=head2 LinkCleanup() 600 601deletes old links from database 602 603return true 604 605 $True = $LinkObject->LinkCleanup( 606 State => 'Temporary', 607 Age => ( 60 * 60 * 24 ), 608 ); 609 610=cut 611 612sub LinkCleanup { 613 my ( $Self, %Param ) = @_; 614 615 # check needed stuff 616 for my $Argument (qw(State Age)) { 617 if ( !$Param{$Argument} ) { 618 $Kernel::OM->Get('Kernel::System::Log')->Log( 619 Priority => 'error', 620 Message => "Need $Argument!", 621 ); 622 return; 623 } 624 } 625 626 # lookup state id 627 my $StateID = $Self->StateLookup( 628 Name => $Param{State}, 629 ); 630 631 return if !$StateID; 632 633 # get time object 634 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 635 636 # calculate delete time 637 $DateTimeObject->Subtract( Seconds => $Param{Age} ); 638 my $DeleteTime = $DateTimeObject->ToString(); 639 640 # delete the link 641 return if !$Kernel::OM->Get('Kernel::System::DB')->Do( 642 SQL => ' 643 DELETE FROM link_relation 644 WHERE state_id = ? 645 AND create_time < ?', 646 Bind => [ 647 \$StateID, \$DeleteTime, 648 ], 649 ); 650 651 # delete cache 652 $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( 653 Type => $Self->{CacheType}, 654 ); 655 656 return 1; 657} 658 659=head2 LinkDelete() 660 661deletes a link 662 663return true 664 665 $True = $LinkObject->LinkDelete( 666 Object1 => 'Ticket', 667 Key1 => '321', 668 Object2 => 'FAQ', 669 Key2 => '5', 670 Type => 'Normal', 671 UserID => 1, 672 ); 673 674=cut 675 676sub LinkDelete { 677 my ( $Self, %Param ) = @_; 678 679 # check needed stuff 680 for my $Argument (qw(Object1 Key1 Object2 Key2 Type UserID)) { 681 if ( !$Param{$Argument} ) { 682 $Kernel::OM->Get('Kernel::System::Log')->Log( 683 Priority => 'error', 684 Message => "Need $Argument!", 685 ); 686 return; 687 } 688 } 689 690 # lookup the object ids 691 OBJECT: 692 for my $Object (qw(Object1 Object2)) { 693 694 # lookup the object id 695 $Param{ $Object . 'ID' } = $Self->ObjectLookup( 696 Name => $Param{$Object}, 697 ); 698 699 next OBJECT if $Param{ $Object . 'ID' }; 700 701 $Kernel::OM->Get('Kernel::System::Log')->Log( 702 Priority => 'error', 703 Message => "Invalid $Object is given!", 704 ); 705 706 return; 707 } 708 709 # lookup type id 710 my $TypeID = $Self->TypeLookup( 711 Name => $Param{Type}, 712 UserID => $Param{UserID}, 713 ); 714 715 # get database object 716 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 717 718 # get the existing link 719 return if !$DBObject->Prepare( 720 SQL => ' 721 SELECT source_object_id, source_key, target_object_id, target_key, state_id 722 FROM link_relation 723 WHERE ( 724 (source_object_id = ? AND source_key = ? 725 AND target_object_id = ? AND target_key = ? ) 726 OR 727 ( source_object_id = ? AND source_key = ? 728 AND target_object_id = ? AND target_key = ? ) 729 ) 730 AND type_id = ?', 731 Bind => [ 732 \$Param{Object1ID}, \$Param{Key1}, 733 \$Param{Object2ID}, \$Param{Key2}, 734 \$Param{Object2ID}, \$Param{Key2}, 735 \$Param{Object1ID}, \$Param{Key1}, 736 \$TypeID, 737 ], 738 Limit => 1, 739 ); 740 741 # fetch results 742 my %Existing; 743 while ( my @Row = $DBObject->FetchrowArray() ) { 744 745 $Existing{SourceObjectID} = $Row[0]; 746 $Existing{SourceKey} = $Row[1]; 747 $Existing{TargetObjectID} = $Row[2]; 748 $Existing{TargetKey} = $Row[3]; 749 $Existing{StateID} = $Row[4]; 750 } 751 752 return 1 if !%Existing; 753 754 # lookup the object names 755 OBJECT: 756 for my $Object (qw(SourceObject TargetObject)) { 757 758 # lookup the object name 759 $Existing{$Object} = $Self->ObjectLookup( 760 ObjectID => $Existing{ $Object . 'ID' }, 761 ); 762 763 next OBJECT if $Existing{$Object}; 764 765 $Kernel::OM->Get('Kernel::System::Log')->Log( 766 Priority => 'error', 767 Message => "Invalid $Object is given!", 768 ); 769 770 return; 771 } 772 773 # lookup state 774 $Existing{State} = $Self->StateLookup( 775 StateID => $Existing{StateID}, 776 ); 777 778 # get backend of source object 779 my $BackendSourceObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Existing{SourceObject} ); 780 781 return if !$BackendSourceObject; 782 783 # get backend of target object 784 my $BackendTargetObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Existing{TargetObject} ); 785 786 return if !$BackendTargetObject; 787 788 # run pre event module of source object 789 $BackendSourceObject->LinkDeletePre( 790 Key => $Existing{SourceKey}, 791 TargetObject => $Existing{TargetObject}, 792 TargetKey => $Existing{TargetKey}, 793 Type => $Param{Type}, 794 State => $Existing{State}, 795 UserID => $Param{UserID}, 796 ); 797 798 # run pre event module of target object 799 $BackendTargetObject->LinkDeletePre( 800 Key => $Existing{TargetKey}, 801 SourceObject => $Existing{SourceObject}, 802 SourceKey => $Existing{SourceKey}, 803 Type => $Param{Type}, 804 State => $Existing{State}, 805 UserID => $Param{UserID}, 806 ); 807 808 # delete the link 809 return if !$DBObject->Do( 810 SQL => ' 811 DELETE FROM link_relation 812 WHERE ( 813 ( source_object_id = ? AND source_key = ? 814 AND target_object_id = ? AND target_key = ? ) 815 OR 816 ( source_object_id = ? AND source_key = ? 817 AND target_object_id = ? AND target_key = ? ) 818 ) 819 AND type_id = ?', 820 Bind => [ 821 \$Param{Object1ID}, \$Param{Key1}, 822 \$Param{Object2ID}, \$Param{Key2}, 823 \$Param{Object2ID}, \$Param{Key2}, 824 \$Param{Object1ID}, \$Param{Key1}, 825 \$TypeID, 826 ], 827 ); 828 829 # delete affected caches (both directions, all states, with/without type) 830 my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); 831 my %StateList = $Self->StateList(); 832 for my $Direction (qw(Source Target)) { 833 for my $DirectionNumber (qw(1 2)) { 834 for my $StateID ( sort keys %StateList ) { 835 my $CacheKey = 836 'Cache::LinkListRaw' 837 . '::Direction' . $Direction 838 . '::ObjectID' . $Param{ 'Object' . $DirectionNumber . 'ID' } 839 . '::StateID' . $StateID; 840 $CacheObject->Delete( 841 Type => $Self->{CacheType}, 842 Key => $CacheKey, 843 ); 844 } 845 } 846 } 847 848 # run post event module of source object 849 $BackendSourceObject->LinkDeletePost( 850 Key => $Existing{SourceKey}, 851 TargetObject => $Existing{TargetObject}, 852 TargetKey => $Existing{TargetKey}, 853 Type => $Param{Type}, 854 State => $Existing{State}, 855 UserID => $Param{UserID}, 856 ); 857 858 # run post event module of target object 859 $BackendTargetObject->LinkDeletePost( 860 Key => $Existing{TargetKey}, 861 SourceObject => $Existing{SourceObject}, 862 SourceKey => $Existing{SourceKey}, 863 Type => $Param{Type}, 864 State => $Existing{State}, 865 UserID => $Param{UserID}, 866 ); 867 868 # Run event handlers. 869 $Self->EventHandler( 870 Event => 'LinkObjectLinkDelete', 871 Data => { 872 SourceObject => $Existing{SourceObject}, 873 SourceKey => $Existing{SourceKey}, 874 TargetObject => $Existing{TargetObject}, 875 TargetKey => $Existing{TargetKey}, 876 Type => $Param{Type}, 877 State => $Existing{State}, 878 }, 879 UserID => $Param{UserID}, 880 ); 881 882 return 1; 883} 884 885=head2 LinkDeleteAll() 886 887delete all links of an object 888 889 $True = $LinkObject->LinkDeleteAll( 890 Object => 'Ticket', 891 Key => '321', 892 UserID => 1, 893 ); 894 895=cut 896 897sub LinkDeleteAll { 898 my ( $Self, %Param ) = @_; 899 900 # check needed stuff 901 for my $Argument (qw(Object Key UserID)) { 902 if ( !$Param{$Argument} ) { 903 $Kernel::OM->Get('Kernel::System::Log')->Log( 904 Priority => 'error', 905 Message => "Need $Argument!", 906 ); 907 return; 908 } 909 } 910 911 # get state list 912 my %StateList = $Self->StateList( 913 Valid => 0, 914 ); 915 916 # delete all links 917 STATE: 918 for my $State ( values %StateList ) { 919 920 # get link list 921 my $LinkList = $Self->LinkList( 922 Object => $Param{Object}, 923 Key => $Param{Key}, 924 State => $State, 925 UserID => $Param{UserID}, 926 ); 927 928 next STATE if !$LinkList; 929 next STATE if !%{$LinkList}; 930 931 for my $Object ( sort keys %{$LinkList} ) { 932 933 for my $LinkType ( sort keys %{ $LinkList->{$Object} } ) { 934 935 # extract link type List 936 my $LinkTypeList = $LinkList->{$Object}->{$LinkType}; 937 938 for my $Direction ( sort keys %{$LinkTypeList} ) { 939 940 # extract direction list 941 my $DirectionList = $LinkList->{$Object}->{$LinkType}->{$Direction}; 942 943 for my $ObjectKey ( sort keys %{$DirectionList} ) { 944 945 # delete the link 946 $Self->LinkDelete( 947 Object1 => $Param{Object}, 948 Key1 => $Param{Key}, 949 Object2 => $Object, 950 Key2 => $ObjectKey, 951 Type => $LinkType, 952 UserID => $Param{UserID}, 953 ); 954 } 955 } 956 } 957 } 958 } 959 960 return 1; 961} 962 963=head2 LinkList() 964 965get all existing links for a given object 966 967Return 968 $LinkList = { 969 Ticket => { 970 Normal => { 971 Source => { 972 12 => 1, 973 212 => 1, 974 332 => 1, 975 }, 976 }, 977 ParentChild => { 978 Source => { 979 5 => 1, 980 9 => 1, 981 }, 982 Target => { 983 4 => 1, 984 8 => 1, 985 15 => 1, 986 }, 987 }, 988 }, 989 FAQ => { 990 ParentChild => { 991 Source => { 992 5 => 1, 993 }, 994 }, 995 }, 996 }; 997 998 my $LinkList = $LinkObject->LinkList( 999 Object => 'Ticket', 1000 Key => '321', 1001 Object2 => 'FAQ', # (optional) 1002 State => 'Valid', 1003 Type => 'ParentChild', # (optional) 1004 Direction => 'Target', # (optional) default Both (Source|Target|Both) 1005 UserID => 1, 1006 ); 1007 1008=cut 1009 1010sub LinkList { 1011 my ( $Self, %Param ) = @_; 1012 1013 # check needed stuff 1014 for my $Argument (qw(Object Key State UserID)) { 1015 if ( !$Param{$Argument} ) { 1016 $Kernel::OM->Get('Kernel::System::Log')->Log( 1017 Priority => 'error', 1018 Message => "Need $Argument!", 1019 ); 1020 return; 1021 } 1022 } 1023 1024 # lookup object id 1025 my $ObjectID = $Self->ObjectLookup( 1026 Name => $Param{Object}, 1027 ); 1028 return if !$ObjectID; 1029 1030 # lookup state id 1031 my $StateID = $Self->StateLookup( 1032 Name => $Param{State}, 1033 ); 1034 1035 # add type id to SQL statement 1036 my $TypeID; 1037 if ( $Param{Type} ) { 1038 1039 # lookup type id 1040 $TypeID = $Self->TypeLookup( 1041 Name => $Param{Type}, 1042 UserID => $Param{UserID}, 1043 ); 1044 } 1045 1046 # get complete list for both directions (or only one if restricted) 1047 my $SourceLinks = {}; 1048 my $TargetLinks = {}; 1049 my $Direction = $Param{Direction} || 'Both'; 1050 if ( $Direction ne 'Target' ) { 1051 $SourceLinks = $Self->_LinkListRaw( 1052 Direction => 'Target', 1053 ObjectID => $ObjectID, 1054 Key => $Param{Key}, 1055 StateID => $StateID, 1056 TypeID => $TypeID, 1057 ); 1058 } 1059 $TargetLinks = $Self->_LinkListRaw( 1060 Direction => 'Source', 1061 ObjectID => $ObjectID, 1062 Key => $Param{Key}, 1063 StateID => $StateID, 1064 TypeID => $TypeID, 1065 ); 1066 1067 # get names for used objects 1068 # consider restriction for Object2 1069 my %ObjectNameLookup; 1070 OBJECTID: 1071 for my $ObjectID ( sort keys %{$SourceLinks}, sort keys %{$TargetLinks} ) { 1072 next OBJECTID if $ObjectNameLookup{$ObjectID}; 1073 1074 # get object name 1075 my $ObjectName = $Self->ObjectLookup( 1076 ObjectID => $ObjectID, 1077 ); 1078 1079 # add to lookup unless restricted 1080 next OBJECTID if $Param{Object2} && $Param{Object2} ne $ObjectName; 1081 $ObjectNameLookup{$ObjectID} = $ObjectName; 1082 } 1083 1084 # shortcut: we have a restriction for Object2 but no matching links 1085 return {} if !%ObjectNameLookup; 1086 1087 # get names and pointed info for used types 1088 my %TypeNameLookup; 1089 my %TypePointedLookup; 1090 for my $ObjectID ( sort keys %ObjectNameLookup ) { 1091 TYPEID: 1092 for my $TypeID ( 1093 sort keys %{ $SourceLinks->{$ObjectID} }, 1094 sort keys %{ $TargetLinks->{$ObjectID} } 1095 ) 1096 { 1097 next TYPEID if $TypeNameLookup{$TypeID}; 1098 1099 # get type name 1100 my %TypeData = $Self->TypeGet( 1101 TypeID => $TypeID, 1102 ); 1103 $TypeNameLookup{$TypeID} = $TypeData{Name}; 1104 $TypePointedLookup{$TypeID} = $TypeData{Pointed}; 1105 } 1106 } 1107 1108 # merge lists, move target keys to source keys for unpointed link types 1109 my %Links; 1110 for my $ObjectID ( sort keys %ObjectNameLookup ) { 1111 my $ObjectName = $ObjectNameLookup{$ObjectID}; 1112 TYPEID: 1113 for my $TypeID ( sort keys %TypeNameLookup ) { 1114 my $HaveSourceLinks = $SourceLinks->{$ObjectID}->{$TypeID} ? 1 : 0; 1115 my $HaveTargetLinks = $TargetLinks->{$ObjectID}->{$TypeID} ? 1 : 0; 1116 my $IsPointed = $TypePointedLookup{$TypeID}; 1117 my $TypeName = $TypeNameLookup{$TypeID}; 1118 1119 # add target links as target 1120 if ( $Direction ne 'Source' && $HaveTargetLinks && $IsPointed ) { 1121 $Links{$ObjectName}->{$TypeName}->{Target} = $TargetLinks->{$ObjectID}->{$TypeID}; 1122 } 1123 1124 next TYPEID if $Direction eq 'Target'; 1125 1126 # add source links as source 1127 if ($HaveSourceLinks) { 1128 $Links{$ObjectName}->{$TypeName}->{Source} = $SourceLinks->{$ObjectID}->{$TypeID}; 1129 } 1130 1131 # add target links as source for non-pointed links 1132 if ( !$IsPointed && $HaveTargetLinks ) { 1133 $Links{$ObjectName}->{$TypeName}->{Source} //= {}; 1134 $Links{$ObjectName}->{$TypeName}->{Source} = { 1135 %{ $Links{$ObjectName}->{$TypeName}->{Source} }, 1136 %{ $TargetLinks->{$ObjectID}->{$TypeID} }, 1137 }; 1138 } 1139 1140 } 1141 } 1142 1143 return \%Links; 1144} 1145 1146=head2 LinkListWithData() 1147 1148get all existing links for a given object with data of the other objects 1149 1150Return 1151 $LinkList = { 1152 Ticket => { 1153 Normal => { 1154 Source => { 1155 12 => $DataOfItem12, 1156 212 => $DataOfItem212, 1157 332 => $DataOfItem332, 1158 }, 1159 }, 1160 ParentChild => { 1161 Source => { 1162 5 => $DataOfItem5, 1163 9 => $DataOfItem9, 1164 }, 1165 Target => { 1166 4 => $DataOfItem4, 1167 8 => $DataOfItem8, 1168 15 => $DataOfItem15, 1169 }, 1170 }, 1171 }, 1172 FAQ => { 1173 ParentChild => { 1174 Source => { 1175 5 => $DataOfItem5, 1176 }, 1177 }, 1178 }, 1179 }; 1180 1181 my $LinkList = $LinkObject->LinkListWithData( 1182 Object => 'Ticket', 1183 Key => '321', 1184 Object2 => 'FAQ', # (optional) 1185 State => 'Valid', 1186 Type => 'ParentChild', # (optional) 1187 Direction => 'Target', # (optional) default Both (Source|Target|Both) 1188 UserID => 1, 1189 ObjectParameters => { # (optional) backend specific flags 1190 Ticket => { 1191 IgnoreLinkedTicketStateTypes => 0|1, 1192 }, 1193 }, 1194 ); 1195 1196=cut 1197 1198sub LinkListWithData { 1199 my ( $Self, %Param ) = @_; 1200 1201 # check needed stuff 1202 for my $Argument (qw(Object Key State UserID)) { 1203 if ( !$Param{$Argument} ) { 1204 $Kernel::OM->Get('Kernel::System::Log')->Log( 1205 Priority => 'error', 1206 Message => "Need $Argument!", 1207 ); 1208 return; 1209 } 1210 } 1211 1212 # get the link list 1213 my $LinkList = $Self->LinkList(%Param); 1214 1215 # check link list 1216 return if !$LinkList; 1217 return if ref $LinkList ne 'HASH'; 1218 1219 # add data to hash 1220 OBJECT: 1221 for my $Object ( sort keys %{$LinkList} ) { 1222 1223 # check if backend object can be loaded 1224 if ( 1225 !$Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::LinkObject::' . $Object ) 1226 ) 1227 { 1228 delete $LinkList->{$Object}; 1229 next OBJECT; 1230 } 1231 1232 # get backend object 1233 my $BackendObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Object ); 1234 1235 # check backend object 1236 if ( !$BackendObject ) { 1237 delete $LinkList->{$Object}; 1238 next OBJECT; 1239 } 1240 1241 my %ObjectParameters = (); 1242 if ( 1243 ref $Param{ObjectParameters} eq 'HASH' 1244 && ref $Param{ObjectParameters}->{$Object} eq 'HASH' 1245 ) 1246 { 1247 %ObjectParameters = %{ $Param{ObjectParameters}->{$Object} }; 1248 } 1249 1250 # add backend data 1251 my $Success = $BackendObject->LinkListWithData( 1252 LinkList => $LinkList->{$Object}, 1253 UserID => $Param{UserID}, 1254 %ObjectParameters, 1255 ); 1256 1257 next OBJECT if $Success; 1258 1259 delete $LinkList->{$Object}; 1260 } 1261 1262 # clean the hash 1263 OBJECT: 1264 for my $Object ( sort keys %{$LinkList} ) { 1265 1266 LINKTYPE: 1267 for my $LinkType ( sort keys %{ $LinkList->{$Object} } ) { 1268 1269 DIRECTION: 1270 for my $Direction ( sort keys %{ $LinkList->{$Object}->{$LinkType} } ) { 1271 1272 next DIRECTION if %{ $LinkList->{$Object}->{$LinkType}->{$Direction} }; 1273 1274 delete $LinkList->{$Object}->{$LinkType}->{$Direction}; 1275 } 1276 1277 next LINKTYPE if %{ $LinkList->{$Object}->{$LinkType} }; 1278 1279 delete $LinkList->{$Object}->{$LinkType}; 1280 } 1281 1282 next OBJECT if %{ $LinkList->{$Object} }; 1283 1284 delete $LinkList->{$Object}; 1285 } 1286 1287 return $LinkList; 1288} 1289 1290=head2 LinkKeyList() 1291 1292return a hash with all existing links of a given object 1293 1294Return 1295 %LinkKeyList = ( 1296 5 => 1, 1297 9 => 1, 1298 12 => 1, 1299 212 => 1, 1300 332 => 1, 1301 ); 1302 1303 my %LinkKeyList = $LinkObject->LinkKeyList( 1304 Object1 => 'Ticket', 1305 Key1 => '321', 1306 Object2 => 'FAQ', 1307 State => 'Valid', 1308 Type => 'ParentChild', # (optional) 1309 Direction => 'Target', # (optional) default Both (Source|Target|Both) 1310 UserID => 1, 1311 ); 1312 1313=cut 1314 1315sub LinkKeyList { 1316 my ( $Self, %Param ) = @_; 1317 1318 # check needed stuff 1319 for my $Argument (qw(Object1 Key1 Object2 State UserID)) { 1320 if ( !$Param{$Argument} ) { 1321 $Kernel::OM->Get('Kernel::System::Log')->Log( 1322 Priority => 'error', 1323 Message => "Need $Argument!", 1324 ); 1325 return; 1326 } 1327 } 1328 1329 # get the link list 1330 my $LinkList = $Self->LinkList( 1331 %Param, 1332 Object => $Param{Object1}, 1333 Key => $Param{Key1}, 1334 ); 1335 1336 # check link list 1337 return if !$LinkList; 1338 return if ref $LinkList ne 'HASH'; 1339 1340 # extract typelist 1341 my $TypeList = $LinkList->{ $Param{Object2} }; 1342 1343 # add data to hash 1344 my %LinkKeyList; 1345 for my $Type ( sort keys %{$TypeList} ) { 1346 1347 # extract direction list 1348 my $DirectionList = $TypeList->{$Type}; 1349 1350 for my $Direction ( sort keys %{$DirectionList} ) { 1351 1352 for my $Key ( sort keys %{ $DirectionList->{$Direction} } ) { 1353 1354 # add key to list 1355 $LinkKeyList{$Key} = $DirectionList->{$Direction}->{$Key}; 1356 } 1357 } 1358 } 1359 1360 return %LinkKeyList; 1361} 1362 1363=head2 LinkKeyListWithData() 1364 1365return a hash with all existing links of a given object 1366 1367Return 1368 %LinkKeyList = ( 1369 5 => $DataOfItem5, 1370 9 => $DataOfItem9, 1371 12 => $DataOfItem12, 1372 212 => $DataOfItem212, 1373 332 => $DataOfItem332, 1374 ); 1375 1376 my %LinkKeyList = $LinkObject->LinkKeyListWithData( 1377 Object1 => 'Ticket', 1378 Key1 => '321', 1379 Object2 => 'FAQ', 1380 State => 'Valid', 1381 Type => 'ParentChild', # (optional) 1382 Direction => 'Target', # (optional) default Both (Source|Target|Both) 1383 UserID => 1, 1384 ); 1385 1386=cut 1387 1388sub LinkKeyListWithData { 1389 my ( $Self, %Param ) = @_; 1390 1391 # check needed stuff 1392 for my $Argument (qw(Object1 Key1 Object2 State UserID)) { 1393 if ( !$Param{$Argument} ) { 1394 $Kernel::OM->Get('Kernel::System::Log')->Log( 1395 Priority => 'error', 1396 Message => "Need $Argument!", 1397 ); 1398 return; 1399 } 1400 } 1401 1402 # get the link list 1403 my $LinkList = $Self->LinkListWithData( 1404 %Param, 1405 Object => $Param{Object1}, 1406 Key => $Param{Key1}, 1407 ); 1408 1409 # check link list 1410 return if !$LinkList; 1411 return if ref $LinkList ne 'HASH'; 1412 1413 # extract typelist 1414 my $TypeList = $LinkList->{ $Param{Object2} }; 1415 1416 # add data to hash 1417 my %LinkKeyList; 1418 for my $Type ( sort keys %{$TypeList} ) { 1419 1420 # extract direction list 1421 my $DirectionList = $TypeList->{$Type}; 1422 1423 for my $Direction ( sort keys %{$DirectionList} ) { 1424 1425 for my $Key ( sort keys %{ $DirectionList->{$Direction} } ) { 1426 1427 # add key to list 1428 $LinkKeyList{$Key} = $DirectionList->{$Direction}->{$Key}; 1429 } 1430 } 1431 } 1432 1433 return %LinkKeyList; 1434} 1435 1436=head2 ObjectLookup() 1437 1438lookup a link object 1439 1440 $ObjectID = $LinkObject->ObjectLookup( 1441 Name => 'Ticket', 1442 ); 1443 1444 or 1445 1446 $Name = $LinkObject->ObjectLookup( 1447 ObjectID => 12, 1448 ); 1449 1450=cut 1451 1452sub ObjectLookup { 1453 my ( $Self, %Param ) = @_; 1454 1455 # check needed stuff 1456 if ( !$Param{ObjectID} && !$Param{Name} ) { 1457 $Kernel::OM->Get('Kernel::System::Log')->Log( 1458 Priority => 'error', 1459 Message => 'Need ObjectID or Name!', 1460 ); 1461 return; 1462 } 1463 1464 if ( $Param{ObjectID} ) { 1465 1466 # check cache 1467 my $CacheKey = 'ObjectLookup::ObjectID::' . $Param{ObjectID}; 1468 my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1469 Type => $Self->{CacheType}, 1470 Key => $CacheKey, 1471 ); 1472 return $Cache if $Cache; 1473 1474 # get database object 1475 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1476 1477 # ask the database 1478 return if !$DBObject->Prepare( 1479 SQL => ' 1480 SELECT name 1481 FROM link_object 1482 WHERE id = ?', 1483 Bind => [ \$Param{ObjectID} ], 1484 Limit => 1, 1485 ); 1486 1487 # fetch the result 1488 my $Name; 1489 while ( my @Row = $DBObject->FetchrowArray() ) { 1490 $Name = $Row[0]; 1491 } 1492 1493 # check the name 1494 if ( !$Name ) { 1495 $Kernel::OM->Get('Kernel::System::Log')->Log( 1496 Priority => 'error', 1497 Message => "Link object id '$Param{ObjectID}' not found in the database!", 1498 ); 1499 return; 1500 } 1501 1502 # set cache 1503 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1504 Type => $Self->{CacheType}, 1505 TTL => $Self->{CacheTTL}, 1506 Key => $CacheKey, 1507 Value => $Name, 1508 ); 1509 1510 return $Name; 1511 } 1512 else { 1513 1514 # check cache 1515 my $CacheKey = 'ObjectLookup::Name::' . $Param{Name}; 1516 my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1517 Type => $Self->{CacheType}, 1518 Key => $CacheKey, 1519 ); 1520 return $Cache if $Cache; 1521 1522 # get needed object 1523 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1524 my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); 1525 1526 # investigate the object id 1527 my $ObjectID; 1528 TRY: 1529 for my $Try ( 1 .. 3 ) { 1530 1531 # ask the database 1532 return if !$DBObject->Prepare( 1533 SQL => ' 1534 SELECT id 1535 FROM link_object 1536 WHERE name = ?', 1537 Bind => [ \$Param{Name} ], 1538 Limit => 1, 1539 ); 1540 1541 # fetch the result 1542 while ( my @Row = $DBObject->FetchrowArray() ) { 1543 $ObjectID = $Row[0]; 1544 } 1545 1546 last TRY if $ObjectID; 1547 1548 # cleanup the given name 1549 $CheckItemObject->StringClean( 1550 StringRef => \$Param{Name}, 1551 ); 1552 1553 # check if name is valid 1554 if ( !$Param{Name} || $Param{Name} =~ m{ :: }xms || $Param{Name} =~ m{ \s }xms ) { 1555 $Kernel::OM->Get('Kernel::System::Log')->Log( 1556 Priority => 'error', 1557 Message => "Invalid object name '$Param{Name}' is given!", 1558 ); 1559 return; 1560 } 1561 1562 next TRY if $Try == 1; 1563 1564 # insert the new object 1565 return if !$DBObject->Do( 1566 SQL => 'INSERT INTO link_object (name) VALUES (?)', 1567 Bind => [ \$Param{Name} ], 1568 ); 1569 } 1570 1571 # set cache 1572 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1573 Type => $Self->{CacheType}, 1574 TTL => $Self->{CacheTTL}, 1575 Key => $CacheKey, 1576 Value => $ObjectID, 1577 ); 1578 1579 return $ObjectID; 1580 } 1581} 1582 1583=head2 TypeLookup() 1584 1585lookup a link type 1586 1587 $TypeID = $LinkObject->TypeLookup( 1588 Name => 'Normal', 1589 UserID => 1, 1590 ); 1591 1592 or 1593 1594 $Name = $LinkObject->TypeLookup( 1595 TypeID => 56, 1596 UserID => 1, 1597 ); 1598 1599=cut 1600 1601sub TypeLookup { 1602 my ( $Self, %Param ) = @_; 1603 1604 # check needed stuff 1605 if ( !$Param{TypeID} && !$Param{Name} ) { 1606 $Kernel::OM->Get('Kernel::System::Log')->Log( 1607 Priority => 'error', 1608 Message => 'Need TypeID or Name!', 1609 ); 1610 return; 1611 } 1612 1613 # check needed stuff 1614 if ( !$Param{UserID} ) { 1615 $Kernel::OM->Get('Kernel::System::Log')->Log( 1616 Priority => 'error', 1617 Message => 'Need UserID!' 1618 ); 1619 return; 1620 } 1621 1622 if ( $Param{TypeID} ) { 1623 1624 # check cache 1625 my $CacheKey = 'TypeLookup::TypeID::' . $Param{TypeID}; 1626 my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1627 Type => $Self->{CacheType}, 1628 Key => $CacheKey, 1629 ); 1630 return $Cache if $Cache; 1631 1632 # get database object 1633 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1634 1635 # ask the database 1636 return if !$DBObject->Prepare( 1637 SQL => 'SELECT name FROM link_type WHERE id = ?', 1638 Bind => [ \$Param{TypeID} ], 1639 Limit => 1, 1640 ); 1641 1642 # fetch the result 1643 my $Name; 1644 while ( my @Row = $DBObject->FetchrowArray() ) { 1645 $Name = $Row[0]; 1646 } 1647 1648 # check the name 1649 if ( !$Name ) { 1650 $Kernel::OM->Get('Kernel::System::Log')->Log( 1651 Priority => 'error', 1652 Message => "Link type id '$Param{TypeID}' not found in the database!", 1653 ); 1654 return; 1655 } 1656 1657 # set cache 1658 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1659 Type => $Self->{CacheType}, 1660 TTL => $Self->{CacheTTL}, 1661 Key => $CacheKey, 1662 Value => $Name, 1663 ); 1664 1665 return $Name; 1666 } 1667 else { 1668 1669 # get check item object 1670 my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); 1671 1672 # cleanup the given name 1673 $CheckItemObject->StringClean( 1674 StringRef => \$Param{Name}, 1675 ); 1676 1677 # check cache 1678 my $CacheKey = 'TypeLookup::Name::' . $Param{Name}; 1679 my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1680 Type => $Self->{CacheType}, 1681 Key => $CacheKey, 1682 ); 1683 return $Cache if $Cache; 1684 1685 # get database object 1686 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1687 1688 # investigate the type id 1689 my $TypeID; 1690 TRY: 1691 for my $Try ( 1 .. 2 ) { 1692 1693 # ask the database 1694 return if !$DBObject->Prepare( 1695 SQL => 'SELECT id FROM link_type WHERE name = ?', 1696 Bind => [ \$Param{Name} ], 1697 Limit => 1, 1698 ); 1699 1700 # fetch the result 1701 while ( my @Row = $DBObject->FetchrowArray() ) { 1702 $TypeID = $Row[0]; 1703 } 1704 1705 last TRY if $TypeID; 1706 1707 # check if name is valid 1708 if ( !$Param{Name} || $Param{Name} =~ m{ :: }xms || $Param{Name} =~ m{ \s }xms ) { 1709 $Kernel::OM->Get('Kernel::System::Log')->Log( 1710 Priority => 'error', 1711 Message => "Invalid type name '$Param{Name}' is given!", 1712 ); 1713 return; 1714 } 1715 1716 # insert the new type 1717 return if !$DBObject->Do( 1718 SQL => ' 1719 INSERT INTO link_type 1720 (name, valid_id, create_time, create_by, change_time, change_by) 1721 VALUES (?, 1, current_timestamp, ?, current_timestamp, ?)', 1722 Bind => [ \$Param{Name}, \$Param{UserID}, \$Param{UserID} ], 1723 ); 1724 } 1725 1726 # check the type id 1727 if ( !$TypeID ) { 1728 $Kernel::OM->Get('Kernel::System::Log')->Log( 1729 Priority => 'error', 1730 Message => "Link type '$Param{Name}' not found in the database!", 1731 ); 1732 return; 1733 } 1734 1735 # set cache 1736 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1737 Type => $Self->{CacheType}, 1738 TTL => $Self->{CacheTTL}, 1739 Key => $CacheKey, 1740 Value => $TypeID, 1741 ); 1742 1743 return $TypeID; 1744 } 1745} 1746 1747=head2 TypeGet() 1748 1749get a link type 1750 1751Return 1752 $TypeData{TypeID} 1753 $TypeData{Name} 1754 $TypeData{SourceName} 1755 $TypeData{TargetName} 1756 $TypeData{Pointed} 1757 $TypeData{CreateTime} 1758 $TypeData{CreateBy} 1759 $TypeData{ChangeTime} 1760 $TypeData{ChangeBy} 1761 1762 %TypeData = $LinkObject->TypeGet( 1763 TypeID => 444, 1764 ); 1765 1766=cut 1767 1768sub TypeGet { 1769 my ( $Self, %Param ) = @_; 1770 1771 # check needed stuff 1772 for my $Argument (qw(TypeID)) { 1773 if ( !$Param{$Argument} ) { 1774 $Kernel::OM->Get('Kernel::System::Log')->Log( 1775 Priority => 'error', 1776 Message => "Need $Argument!", 1777 ); 1778 return; 1779 } 1780 } 1781 1782 # check cache 1783 my $CacheKey = 'TypeGet::TypeID::' . $Param{TypeID}; 1784 my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1785 Type => $Self->{CacheType}, 1786 Key => $CacheKey, 1787 ); 1788 return %{$Cache} if $Cache; 1789 1790 # get database object 1791 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1792 1793 # ask the database 1794 return if !$DBObject->Prepare( 1795 SQL => ' 1796 SELECT id, name, create_time, create_by, change_time, change_by 1797 FROM link_type 1798 WHERE id = ?', 1799 Bind => [ \$Param{TypeID} ], 1800 Limit => 1, 1801 ); 1802 1803 # fetch the result 1804 my %Type; 1805 while ( my @Row = $DBObject->FetchrowArray() ) { 1806 $Type{TypeID} = $Row[0]; 1807 $Type{Name} = $Row[1]; 1808 $Type{CreateTime} = $Row[2]; 1809 $Type{CreateBy} = $Row[3]; 1810 $Type{ChangeTime} = $Row[4]; 1811 $Type{ChangeBy} = $Row[5]; 1812 } 1813 1814 # get config of all types 1815 my $ConfiguredTypes = $Kernel::OM->Get('Kernel::Config')->Get('LinkObject::Type'); 1816 1817 # check the config 1818 if ( !$ConfiguredTypes->{ $Type{Name} } ) { 1819 $Kernel::OM->Get('Kernel::System::Log')->Log( 1820 Priority => 'error', 1821 Message => "Linktype '$Type{Name}' does not exist!", 1822 ); 1823 return; 1824 } 1825 1826 # add source and target name 1827 $Type{SourceName} = $ConfiguredTypes->{ $Type{Name} }->{SourceName} || ''; 1828 $Type{TargetName} = $ConfiguredTypes->{ $Type{Name} }->{TargetName} || ''; 1829 1830 # get check item object 1831 my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); 1832 1833 # clean the names 1834 ARGUMENT: 1835 for my $Argument (qw(SourceName TargetName)) { 1836 $CheckItemObject->StringClean( 1837 StringRef => \$Type{$Argument}, 1838 RemoveAllNewlines => 1, 1839 RemoveAllTabs => 1, 1840 ); 1841 1842 next ARGUMENT if $Type{$Argument}; 1843 1844 $Kernel::OM->Get('Kernel::System::Log')->Log( 1845 Priority => 'error', 1846 Message => 1847 "The $Argument '$Type{$Argument}' is invalid in SysConfig (LinkObject::Type)!", 1848 ); 1849 return; 1850 } 1851 1852 # add pointed value 1853 $Type{Pointed} = $Type{SourceName} ne $Type{TargetName} ? 1 : 0; 1854 1855 # set cache 1856 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1857 Type => $Self->{CacheType}, 1858 TTL => $Self->{CacheTTL}, 1859 Key => $CacheKey, 1860 Value => \%Type, 1861 ); 1862 1863 return %Type; 1864} 1865 1866=head2 TypeList() 1867 1868return a 2 dimensional hash list of all valid link types 1869 1870Return 1871 $TypeList{ 1872 Normal => { 1873 SourceName => 'Normal', 1874 TargetName => 'Normal', 1875 }, 1876 ParentChild => { 1877 SourceName => 'Parent', 1878 TargetName => 'Child', 1879 }, 1880 } 1881 1882 my %TypeList = $LinkObject->TypeList(); 1883 1884=cut 1885 1886sub TypeList { 1887 my ( $Self, %Param ) = @_; 1888 1889 # get type list 1890 my $TypeListRef = $Kernel::OM->Get('Kernel::Config')->Get('LinkObject::Type') || {}; 1891 my %TypeList = %{$TypeListRef}; 1892 1893 # get check item object 1894 my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); 1895 1896 # prepare the type list 1897 TYPE: 1898 for my $Type ( sort keys %TypeList ) { 1899 1900 # check the source and target name 1901 ARGUMENT: 1902 for my $Argument (qw(SourceName TargetName)) { 1903 1904 # set empty string as default value 1905 $TypeList{$Type}{$Argument} ||= ''; 1906 1907 # clean the argument 1908 $CheckItemObject->StringClean( 1909 StringRef => \$TypeList{$Type}{$Argument}, 1910 RemoveAllNewlines => 1, 1911 RemoveAllTabs => 1, 1912 ); 1913 1914 next ARGUMENT if $TypeList{$Type}{$Argument}; 1915 1916 # remove invalid link type from list 1917 delete $TypeList{$Type}; 1918 1919 next TYPE; 1920 } 1921 } 1922 1923 return %TypeList; 1924} 1925 1926=head2 TypeGroupList() 1927 1928return a 2 dimensional hash list of all type groups 1929 1930Return 1931 %TypeGroupList = ( 1932 001 => [ 1933 'Normal', 1934 'ParentChild', 1935 ], 1936 002 => [ 1937 'Normal', 1938 'DependsOn', 1939 ], 1940 003 => [ 1941 'ParentChild', 1942 'RelevantTo', 1943 ], 1944 ); 1945 1946 my %TypeGroupList = $LinkObject->TypeGroupList(); 1947 1948=cut 1949 1950sub TypeGroupList { 1951 my ( $Self, %Param ) = @_; 1952 1953 # get possible type groups 1954 my $TypeGroupListRef = $Kernel::OM->Get('Kernel::Config')->Get('LinkObject::TypeGroup') || {}; 1955 my %TypeGroupList = %{$TypeGroupListRef}; 1956 1957 # get check item object 1958 my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); 1959 1960 # prepare the possible link list 1961 TYPEGROUP: 1962 for my $TypeGroup ( sort keys %TypeGroupList ) { 1963 1964 # check the types 1965 TYPE: 1966 for my $Type ( @{ $TypeGroupList{$TypeGroup} } ) { 1967 1968 # set empty string as default value 1969 $Type ||= ''; 1970 1971 # trim the argument 1972 $CheckItemObject->StringClean( 1973 StringRef => \$Type, 1974 ); 1975 1976 next TYPE if $Type && $Type !~ m{ :: }xms && $Type !~ m{ \s }xms; 1977 1978 # log the error 1979 $Kernel::OM->Get('Kernel::System::Log')->Log( 1980 Priority => 'error', 1981 Message => 1982 "The Argument '$Type' is invalid in SysConfig (LinkObject::TypeGroup)!", 1983 ); 1984 1985 # remove entry from list if it is invalid 1986 delete $TypeGroupList{$TypeGroup}; 1987 1988 next TYPEGROUP; 1989 } 1990 } 1991 1992 # get type list 1993 my %TypeList = $Self->TypeList(); 1994 1995 # check types 1996 TYPEGROUP: 1997 for my $TypeGroup ( sort keys %TypeGroupList ) { 1998 1999 # check the types 2000 TYPE: 2001 for my $Type ( @{ $TypeGroupList{$TypeGroup} } ) { 2002 2003 # set empty string as default value 2004 $Type ||= ''; 2005 2006 next TYPE if $TypeList{$Type}; 2007 2008 # log the error 2009 $Kernel::OM->Get('Kernel::System::Log')->Log( 2010 Priority => 'error', 2011 Message => 2012 "The LinkType '$Type' is invalid in SysConfig (LinkObject::TypeGroup)!", 2013 ); 2014 2015 # remove entry from list if type doesn't exist 2016 delete $TypeGroupList{$TypeGroup}; 2017 2018 next TYPEGROUP; 2019 } 2020 } 2021 2022 return %TypeGroupList; 2023} 2024 2025=head2 PossibleType() 2026 2027return true if both types are NOT together in a type group 2028 2029 my $True = $LinkObject->PossibleType( 2030 Type1 => 'Normal', 2031 Type2 => 'ParentChild', 2032 ); 2033 2034=cut 2035 2036sub PossibleType { 2037 my ( $Self, %Param ) = @_; 2038 2039 # check needed stuff 2040 for my $Argument (qw(Type1 Type2)) { 2041 if ( !$Param{$Argument} ) { 2042 $Kernel::OM->Get('Kernel::System::Log')->Log( 2043 Priority => 'error', 2044 Message => "Need $Argument!", 2045 ); 2046 return; 2047 } 2048 } 2049 2050 # get type group list 2051 my %TypeGroupList = $Self->TypeGroupList(); 2052 2053 # check all type groups 2054 TYPEGROUP: 2055 for my $TypeGroup ( sort keys %TypeGroupList ) { 2056 2057 my %TypeList = map { $_ => 1 } @{ $TypeGroupList{$TypeGroup} }; 2058 2059 return if $TypeList{ $Param{Type1} } && $TypeList{ $Param{Type2} }; 2060 2061 } 2062 2063 return 1; 2064} 2065 2066=head2 StateLookup() 2067 2068lookup a link state 2069 2070 $StateID = $LinkObject->StateLookup( 2071 Name => 'Valid', 2072 ); 2073 2074 or 2075 2076 $Name = $LinkObject->StateLookup( 2077 StateID => 56, 2078 ); 2079 2080=cut 2081 2082sub StateLookup { 2083 my ( $Self, %Param ) = @_; 2084 2085 # check needed stuff 2086 if ( !$Param{StateID} && !$Param{Name} ) { 2087 $Kernel::OM->Get('Kernel::System::Log')->Log( 2088 Priority => 'error', 2089 Message => 'Need StateID or Name!', 2090 ); 2091 return; 2092 } 2093 2094 if ( $Param{StateID} ) { 2095 2096 # check cache 2097 my $CacheKey = 'StateLookup::StateID::' . $Param{StateID}; 2098 my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( 2099 Type => $Self->{CacheType}, 2100 Key => $CacheKey, 2101 ); 2102 return $Cache if $Cache; 2103 2104 # get database object 2105 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2106 2107 # ask the database 2108 return if !$DBObject->Prepare( 2109 SQL => ' 2110 SELECT name 2111 FROM link_state 2112 WHERE id = ?', 2113 Bind => [ \$Param{StateID} ], 2114 Limit => 1, 2115 ); 2116 2117 # fetch the result 2118 my $Name; 2119 while ( my @Row = $DBObject->FetchrowArray() ) { 2120 $Name = $Row[0]; 2121 } 2122 2123 # check the name 2124 if ( !$Name ) { 2125 $Kernel::OM->Get('Kernel::System::Log')->Log( 2126 Priority => 'error', 2127 Message => "Link state id '$Param{StateID}' not found in the database!", 2128 ); 2129 return; 2130 } 2131 2132 # set cache 2133 $Kernel::OM->Get('Kernel::System::Cache')->Set( 2134 Type => $Self->{CacheType}, 2135 TTL => $Self->{CacheTTL}, 2136 Key => $CacheKey, 2137 Value => $Name, 2138 ); 2139 2140 return $Name; 2141 } 2142 else { 2143 2144 # check cache 2145 my $CacheKey = 'StateLookup::Name::' . $Param{Name}; 2146 my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( 2147 Type => $Self->{CacheType}, 2148 Key => $CacheKey, 2149 ); 2150 return $Cache if $Cache; 2151 2152 # get database object 2153 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2154 2155 # ask the database 2156 return if !$DBObject->Prepare( 2157 SQL => ' 2158 SELECT id 2159 FROM link_state 2160 WHERE name = ?', 2161 Bind => [ \$Param{Name} ], 2162 Limit => 1, 2163 ); 2164 2165 # fetch the result 2166 my $StateID; 2167 while ( my @Row = $DBObject->FetchrowArray() ) { 2168 $StateID = $Row[0]; 2169 } 2170 2171 # check the state id 2172 if ( !$StateID ) { 2173 $Kernel::OM->Get('Kernel::System::Log')->Log( 2174 Priority => 'error', 2175 Message => "Link state '$Param{Name}' not found in the database!", 2176 ); 2177 return; 2178 } 2179 2180 # set cache 2181 $Kernel::OM->Get('Kernel::System::Cache')->Set( 2182 Type => $Self->{CacheType}, 2183 TTL => $Self->{CacheTTL}, 2184 Key => $CacheKey, 2185 Value => $StateID, 2186 ); 2187 2188 return $StateID; 2189 } 2190} 2191 2192=head2 StateList() 2193 2194return a hash list of all valid link states 2195 2196Return 2197 $StateList{ 2198 4 => 'Valid', 2199 8 => 'Temporary', 2200 } 2201 2202 my %StateList = $LinkObject->StateList( 2203 Valid => 0, # (optional) default 1 (0|1) 2204 ); 2205 2206=cut 2207 2208sub StateList { 2209 my ( $Self, %Param ) = @_; 2210 2211 # set valid param 2212 if ( !defined $Param{Valid} ) { 2213 $Param{Valid} = 1; 2214 } 2215 2216 # add valid part 2217 my $SQLWhere = ''; 2218 if ( $Param{Valid} ) { 2219 2220 # create the valid id string 2221 my $ValidIDs = join ', ', $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet(); 2222 2223 $SQLWhere = "WHERE valid_id IN ( $ValidIDs )"; 2224 } 2225 2226 # get database object 2227 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2228 2229 # ask database 2230 return if !$DBObject->Prepare( 2231 SQL => "SELECT id, name FROM link_state $SQLWhere", 2232 ); 2233 2234 # fetch the result 2235 my %StateList; 2236 while ( my @Row = $DBObject->FetchrowArray() ) { 2237 $StateList{ $Row[0] } = $Row[1]; 2238 } 2239 2240 return %StateList; 2241} 2242 2243=head2 ObjectPermission() 2244 2245checks read permission for a given object and UserID. 2246 2247 $Permission = $LinkObject->ObjectPermission( 2248 Object => 'Ticket', 2249 Key => 123, 2250 UserID => 1, 2251 ); 2252 2253=cut 2254 2255sub ObjectPermission { 2256 my ( $Self, %Param ) = @_; 2257 2258 # check needed stuff 2259 for my $Argument (qw(Object Key UserID)) { 2260 if ( !$Param{$Argument} ) { 2261 $Kernel::OM->Get('Kernel::System::Log')->Log( 2262 Priority => 'error', 2263 Message => "Need $Argument!", 2264 ); 2265 return; 2266 } 2267 } 2268 2269 # get backend object 2270 my $BackendObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{Object} ); 2271 2272 return if !$BackendObject; 2273 return 1 if !$BackendObject->can('ObjectPermission'); 2274 2275 return $BackendObject->ObjectPermission( 2276 %Param, 2277 ); 2278} 2279 2280=head2 ObjectDescriptionGet() 2281 2282return a hash of object descriptions 2283 2284Return 2285 %Description = ( 2286 Normal => '', 2287 Long => '', 2288 ); 2289 2290 %Description = $LinkObject->ObjectDescriptionGet( 2291 Object => 'Ticket', 2292 Key => 123, 2293 UserID => 1, 2294 ); 2295 2296=cut 2297 2298sub ObjectDescriptionGet { 2299 my ( $Self, %Param ) = @_; 2300 2301 # check needed stuff 2302 for my $Argument (qw(Object Key UserID)) { 2303 if ( !$Param{$Argument} ) { 2304 $Kernel::OM->Get('Kernel::System::Log')->Log( 2305 Priority => 'error', 2306 Message => "Need $Argument!", 2307 ); 2308 return; 2309 } 2310 } 2311 2312 # get backend object 2313 my $BackendObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{Object} ); 2314 2315 return if !$BackendObject; 2316 2317 # get object description 2318 my %Description = $BackendObject->ObjectDescriptionGet( 2319 %Param, 2320 ); 2321 2322 return %Description; 2323} 2324 2325=head2 ObjectSearch() 2326 2327return a hash reference of the search results. 2328 2329Returns: 2330 2331 $ObjectList = { 2332 Ticket => { 2333 NOTLINKED => { 2334 Source => { 2335 12 => $DataOfItem12, 2336 212 => $DataOfItem212, 2337 332 => $DataOfItem332, 2338 }, 2339 }, 2340 }, 2341 }; 2342 2343 $ObjectList = $LinkObject->ObjectSearch( 2344 Object => 'ITSMConfigItem', 2345 SubObject => 'Computer' # (optional) 2346 SearchParams => $HashRef, # (optional) 2347 UserID => 1, 2348 ); 2349 2350=cut 2351 2352sub ObjectSearch { 2353 my ( $Self, %Param ) = @_; 2354 2355 # check needed stuff 2356 for my $Argument (qw(Object UserID)) { 2357 if ( !$Param{$Argument} ) { 2358 $Kernel::OM->Get('Kernel::System::Log')->Log( 2359 Priority => 'error', 2360 Message => "Need $Argument!", 2361 ); 2362 return; 2363 } 2364 } 2365 2366 # get backend object 2367 my $BackendObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{Object} ); 2368 2369 return if !$BackendObject; 2370 2371 # search objects 2372 my $SearchList = $BackendObject->ObjectSearch( 2373 %Param, 2374 ); 2375 2376 my %ObjectList; 2377 $ObjectList{ $Param{Object} } = $SearchList; 2378 2379 return \%ObjectList; 2380} 2381 2382sub _LinkListRaw { 2383 my ( $Self, %Param ) = @_; 2384 2385 # check needed stuff 2386 for my $Argument (qw(Direction ObjectID Key StateID)) { 2387 if ( !$Param{$Argument} ) { 2388 $Kernel::OM->Get('Kernel::System::Log')->Log( 2389 Priority => 'error', 2390 Message => "Need $Argument!", 2391 ); 2392 return; 2393 } 2394 } 2395 2396 # check cache 2397 my $CacheKey = 2398 'Cache::LinkListRaw' 2399 . '::Direction' . $Param{Direction} 2400 . '::ObjectID' . $Param{ObjectID} 2401 . '::StateID' . $Param{StateID}; 2402 my $CachedLinks = $Kernel::OM->Get('Kernel::System::Cache')->Get( 2403 Type => $Self->{CacheType}, 2404 Key => $CacheKey, 2405 ); 2406 2407 my @Links; 2408 if ( ref $CachedLinks eq 'ARRAY' ) { 2409 @Links = @{$CachedLinks}; 2410 } 2411 else { 2412 2413 # prepare SQL statement 2414 my $TypeSQL = ''; 2415 my @Bind = ( \$Param{ObjectID}, \$Param{StateID} ); 2416 2417 # get fields based on type 2418 my $SQL; 2419 if ( $Param{Direction} eq 'Source' ) { 2420 $SQL = 2421 'SELECT target_object_id, target_key, type_id, source_key' 2422 . ' FROM link_relation' 2423 . ' WHERE source_object_id = ?'; 2424 } 2425 else { 2426 $SQL = 2427 'SELECT source_object_id, source_key, type_id, target_key' 2428 . ' FROM link_relation' 2429 . ' WHERE target_object_id = ?'; 2430 } 2431 $SQL .= ' AND state_id = ?' . $TypeSQL; 2432 2433 # get all links for object/state/type (for better caching) 2434 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 2435 return if !$DBObject->Prepare( 2436 SQL => $SQL, 2437 Bind => \@Bind, 2438 ); 2439 2440 # fetch results 2441 while ( my @Row = $DBObject->FetchrowArray() ) { 2442 push @Links, { 2443 ObjectID => $Row[0], 2444 ResponseKey => $Row[1], 2445 TypeID => $Row[2], 2446 RequestKey => $Row[3], 2447 }; 2448 } 2449 2450 # set cache 2451 $Kernel::OM->Get('Kernel::System::Cache')->Set( 2452 Type => $Self->{CacheType}, 2453 TTL => $Self->{CacheTTL}, 2454 Key => $CacheKey, 2455 2456 # make a local copy of the data to avoid it being altered in-memory later 2457 Value => [@Links], 2458 ); 2459 } 2460 2461 # fill result hash and if necessary filter by specified key and/or type id 2462 my %List; 2463 LINK: 2464 for my $Link (@Links) { 2465 next LINK if $Link->{RequestKey} ne $Param{Key}; 2466 next LINK if $Param{TypeID} && $Link->{TypeID} ne $Param{TypeID}; 2467 $List{ $Link->{ObjectID} }->{ $Link->{TypeID} }->{ $Link->{ResponseKey} } = 1; 2468 } 2469 2470 return \%List; 2471} 2472 24731; 2474 2475=head1 TERMS AND CONDITIONS 2476 2477This software is part of the OTRS project (L<https://otrs.org/>). 2478 2479This software comes with ABSOLUTELY NO WARRANTY. For details, see 2480the enclosed file COPYING for license information (GPL). If you 2481did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 2482 2483=cut 2484