# -- # Copyright (C) 2001-2019 OTRS AG, https://otrs.com/ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you # did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. # -- package Kernel::System::LinkObject; use strict; use warnings; use parent qw( Kernel::System::EventHandler ); our @ObjectDependencies = ( 'Kernel::Config', 'Kernel::System::Cache', 'Kernel::System::CheckItem', 'Kernel::System::DateTime', 'Kernel::System::DB', 'Kernel::System::Log', 'Kernel::System::Main', 'Kernel::System::Valid', ); =head1 NAME Kernel::System::LinkObject - to link objects like tickets, faq entries, ... =head1 DESCRIPTION All functions to link objects like tickets, faq entries, ... =head1 PUBLIC INTERFACE =head2 new() Don't use the constructor directly, use the ObjectManager instead: my $LinkObject = $Kernel::OM->Get('Kernel::System::LinkObject'); =cut sub new { my ( $Type, %Param ) = @_; my $Self = {}; bless( $Self, $Type ); # Initialize event handler. $Self->EventHandlerInit( Config => 'LinkObject::EventModulePost', ); $Self->{CacheType} = 'LinkObject'; $Self->{CacheTTL} = 60 * 60 * 24 * 20; return $Self; } =head2 PossibleTypesList() return a hash of all possible types Return %PossibleTypesList = ( 'Normal' => 1, 'ParentChild' => 1, ); my %PossibleTypesList = $LinkObject->PossibleTypesList( Object1 => 'Ticket', Object2 => 'FAQ', ); =cut sub PossibleTypesList { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object1 Object2)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get possible link list my %PossibleLinkList = $Self->PossibleLinkList(); # remove not needed entries POSSIBLELINK: for my $PossibleLink ( sort keys %PossibleLinkList ) { # extract objects my $Object1 = $PossibleLinkList{$PossibleLink}->{Object1}; my $Object2 = $PossibleLinkList{$PossibleLink}->{Object2}; next POSSIBLELINK if ( $Object1 eq $Param{Object1} && $Object2 eq $Param{Object2} ) || ( $Object2 eq $Param{Object1} && $Object1 eq $Param{Object2} ); # remove entry from list if objects don't match delete $PossibleLinkList{$PossibleLink}; } # get type list my %TypeList = $Self->TypeList(); # check types POSSIBLELINK: for my $PossibleLink ( sort keys %PossibleLinkList ) { # extract type my $Type = $PossibleLinkList{$PossibleLink}->{Type} || ''; next POSSIBLELINK if $TypeList{$Type}; # remove entry from list if type doesn't exist delete $PossibleLinkList{$PossibleLink}; } # extract the type list my %PossibleTypesList; for my $PossibleLink ( sort keys %PossibleLinkList ) { # extract type my $Type = $PossibleLinkList{$PossibleLink}->{Type}; $PossibleTypesList{$Type} = 1; } return %PossibleTypesList; } =head2 PossibleObjectsList() return a hash of all possible objects Return %PossibleObjectsList = ( 'Ticket' => 1, 'FAQ' => 1, ); my %PossibleObjectsList = $LinkObject->PossibleObjectsList( Object => 'Ticket', ); =cut sub PossibleObjectsList { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get possible link list my %PossibleLinkList = $Self->PossibleLinkList(); # investigate the possible object list my %PossibleObjectsList; POSSIBLELINK: for my $PossibleLink ( sort keys %PossibleLinkList ) { # extract objects my $Object1 = $PossibleLinkList{$PossibleLink}->{Object1}; my $Object2 = $PossibleLinkList{$PossibleLink}->{Object2}; next POSSIBLELINK if $Param{Object} ne $Object1 && $Param{Object} ne $Object2; # add object to list if ( $Param{Object} eq $Object1 ) { $PossibleObjectsList{$Object2} = 1; } else { $PossibleObjectsList{$Object1} = 1; } } return %PossibleObjectsList; } =head2 PossibleLinkList() return a 2 dimensional hash list of all possible links Return %PossibleLinkList = ( 001 => { Object1 => 'Ticket', Object2 => 'Ticket', Type => 'Normal', }, 002 => { Object1 => 'Ticket', Object2 => 'Ticket', Type => 'ParentChild', }, ); my %PossibleLinkList = $LinkObject->PossibleLinkList(); =cut sub PossibleLinkList { my ( $Self, %Param ) = @_; # get needed objects my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); # get possible link list my $PossibleLinkListRef = $ConfigObject->Get('LinkObject::PossibleLink') || {}; my %PossibleLinkList = %{$PossibleLinkListRef}; # prepare the possible link list POSSIBLELINK: for my $PossibleLink ( sort keys %PossibleLinkList ) { # check the object1, object2 and type string ARGUMENT: for my $Argument (qw(Object1 Object2 Type)) { # set empty string as default value $PossibleLinkList{$PossibleLink}->{$Argument} ||= ''; # trim the argument $CheckItemObject->StringClean( StringRef => \$PossibleLinkList{$PossibleLink}->{$Argument}, ); # extract value my $Value = $PossibleLinkList{$PossibleLink}->{$Argument} || ''; next ARGUMENT if $Value && $Value !~ m{ :: }xms && $Value !~ m{ \s }xms; # log the error $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "The $Argument '$Value' is invalid in SysConfig (LinkObject::PossibleLink)!", ); # remove entry from list if it is invalid delete $PossibleLinkList{$PossibleLink}; next POSSIBLELINK; } } # get location of the backend modules my $BackendLocation = $ConfigObject->Get('Home') . '/Kernel/System/LinkObject/'; # check the existing objects POSSIBLELINK: for my $PossibleLink ( sort keys %PossibleLinkList ) { # check if object backends exist ARGUMENT: for my $Argument (qw(Object1 Object2)) { # extract object my $Object = $PossibleLinkList{$PossibleLink}->{$Argument}; next ARGUMENT if -e $BackendLocation . $Object . '.pm'; # remove entry from list if it is invalid delete $PossibleLinkList{$PossibleLink}; next POSSIBLELINK; } } # get type list my %TypeList = $Self->TypeList(); # check types POSSIBLELINK: for my $PossibleLink ( sort keys %PossibleLinkList ) { # extract type my $Type = $PossibleLinkList{$PossibleLink}->{Type}; next POSSIBLELINK if $TypeList{$Type}; # log the error $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "The LinkType '$Type' is invalid in SysConfig (LinkObject::PossibleLink)!", ); # remove entry from list if type doesn't exist delete $PossibleLinkList{$PossibleLink}; } return %PossibleLinkList; } =head2 LinkAdd() add a new link between two elements $True = $LinkObject->LinkAdd( SourceObject => 'Ticket', SourceKey => '321', TargetObject => 'FAQ', TargetKey => '5', Type => 'ParentChild', State => 'Valid', UserID => 1, ); =cut sub LinkAdd { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(SourceObject SourceKey TargetObject TargetKey Type State UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # check if source and target are the same object if ( $Param{SourceObject} eq $Param{TargetObject} && $Param{SourceKey} eq $Param{TargetKey} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Impossible to link object with itself!', ); return; } # lookup the object ids OBJECT: for my $Object (qw(SourceObject TargetObject)) { # lookup the object id $Param{ $Object . 'ID' } = $Self->ObjectLookup( Name => $Param{$Object}, ); next OBJECT if $Param{ $Object . 'ID' }; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Invalid $Object is given!", ); return; } # get a list of possible link types for the two objects my %PossibleTypesList = $Self->PossibleTypesList( Object1 => $Param{SourceObject}, Object2 => $Param{TargetObject}, ); # check if wanted link type is possible if ( !$PossibleTypesList{ $Param{Type} } ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Not possible to create a '$Param{Type}' link between $Param{SourceObject} and $Param{TargetObject}!", ); return; } # lookup state id my $StateID = $Self->StateLookup( Name => $Param{State}, ); # lookup type id my $TypeID = $Self->TypeLookup( Name => $Param{Type}, UserID => $Param{UserID}, ); # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # check if link already exists in database return if !$DBObject->Prepare( SQL => ' SELECT source_object_id, source_key, state_id FROM link_relation WHERE ( ( source_object_id = ? AND source_key = ? AND target_object_id = ? AND target_key = ? ) OR ( source_object_id = ? AND source_key = ? AND target_object_id = ? AND target_key = ? ) ) AND type_id = ?', Bind => [ \$Param{SourceObjectID}, \$Param{SourceKey}, \$Param{TargetObjectID}, \$Param{TargetKey}, \$Param{TargetObjectID}, \$Param{TargetKey}, \$Param{SourceObjectID}, \$Param{SourceKey}, \$TypeID, ], Limit => 1, ); # fetch the result my %Existing; while ( my @Row = $DBObject->FetchrowArray() ) { $Existing{SourceObjectID} = $Row[0]; $Existing{SourceKey} = $Row[1]; $Existing{StateID} = $Row[2]; } # link exists already if (%Existing) { # existing link has a different StateID than the new link if ( $Existing{StateID} ne $StateID ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Link already exists between these two objects " . "with a different state id '$Existing{StateID}'!", ); return; } # get type data my %TypeData = $Self->TypeGet( TypeID => $TypeID, ); return 1 if !$TypeData{Pointed}; return 1 if $Existing{SourceObjectID} eq $Param{SourceObjectID} && $Existing{SourceKey} eq $Param{SourceKey}; # log error $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Link already exists between these two objects in opposite direction!', ); return; } # get all links that the source object already has my $Links = $Self->LinkList( Object => $Param{SourceObject}, Key => $Param{SourceKey}, State => $Param{State}, UserID => $Param{UserID}, ); # check type groups OBJECT: for my $Object ( sort keys %{$Links} ) { next OBJECT if $Object ne $Param{TargetObject}; TYPE: for my $Type ( sort keys %{ $Links->{$Object} } ) { # extract source and target my $Source = $Links->{$Object}->{$Type}->{Source} ||= {}; my $Target = $Links->{$Object}->{$Type}->{Target} ||= {}; # check if source and target object are already linked next TYPE if !$Source->{ $Param{TargetKey} } && !$Target->{ $Param{TargetKey} }; # check the type groups my $TypeGroupCheck = $Self->PossibleType( Type1 => $Type, Type2 => $Param{Type}, ); next TYPE if $TypeGroupCheck; # existing link type is in a type group with the new link $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Another Link already exists within the same type group!', ); return; } } # get backend of source object my $BackendSourceObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{SourceObject} ); return if !$BackendSourceObject; # get backend of target object my $BackendTargetObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{TargetObject} ); return if !$BackendTargetObject; # run pre event module of source object $BackendSourceObject->LinkAddPre( Key => $Param{SourceKey}, TargetObject => $Param{TargetObject}, TargetKey => $Param{TargetKey}, Type => $Param{Type}, State => $Param{State}, UserID => $Param{UserID}, ); # run pre event module of target object $BackendTargetObject->LinkAddPre( Key => $Param{TargetKey}, SourceObject => $Param{SourceObject}, SourceKey => $Param{SourceKey}, Type => $Param{Type}, State => $Param{State}, UserID => $Param{UserID}, ); return if !$DBObject->Do( SQL => ' INSERT INTO link_relation (source_object_id, source_key, target_object_id, target_key, type_id, state_id, create_time, create_by) VALUES (?, ?, ?, ?, ?, ?, current_timestamp, ?)', Bind => [ \$Param{SourceObjectID}, \$Param{SourceKey}, \$Param{TargetObjectID}, \$Param{TargetKey}, \$TypeID, \$StateID, \$Param{UserID}, ], ); # delete affected caches (both directions) my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); for my $Direction (qw(Source Target)) { my $CacheKey = 'Cache::LinkListRaw' . '::Direction' . $Direction . '::ObjectID' . $Param{ $Direction . 'ObjectID' } . '::StateID' . $StateID; $CacheObject->Delete( Type => $Self->{CacheType}, Key => $CacheKey, ); } # run post event module of source object $BackendSourceObject->LinkAddPost( Key => $Param{SourceKey}, TargetObject => $Param{TargetObject}, TargetKey => $Param{TargetKey}, Type => $Param{Type}, State => $Param{State}, UserID => $Param{UserID}, ); # run post event module of target object $BackendTargetObject->LinkAddPost( Key => $Param{TargetKey}, SourceObject => $Param{SourceObject}, SourceKey => $Param{SourceKey}, Type => $Param{Type}, State => $Param{State}, UserID => $Param{UserID}, ); # Run event handlers. $Self->EventHandler( Event => 'LinkObjectLinkAdd', Data => { SourceObject => $Param{SourceObject}, SourceKey => $Param{SourceKey}, TargetObject => $Param{TargetObject}, TargetKey => $Param{TargetKey}, Type => $Param{Type}, State => $Param{State}, }, UserID => $Param{UserID}, ); return 1; } =head2 LinkCleanup() deletes old links from database return true $True = $LinkObject->LinkCleanup( State => 'Temporary', Age => ( 60 * 60 * 24 ), ); =cut sub LinkCleanup { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(State Age)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # lookup state id my $StateID = $Self->StateLookup( Name => $Param{State}, ); return if !$StateID; # get time object my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); # calculate delete time $DateTimeObject->Subtract( Seconds => $Param{Age} ); my $DeleteTime = $DateTimeObject->ToString(); # delete the link return if !$Kernel::OM->Get('Kernel::System::DB')->Do( SQL => ' DELETE FROM link_relation WHERE state_id = ? AND create_time < ?', Bind => [ \$StateID, \$DeleteTime, ], ); # delete cache $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( Type => $Self->{CacheType}, ); return 1; } =head2 LinkDelete() deletes a link return true $True = $LinkObject->LinkDelete( Object1 => 'Ticket', Key1 => '321', Object2 => 'FAQ', Key2 => '5', Type => 'Normal', UserID => 1, ); =cut sub LinkDelete { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object1 Key1 Object2 Key2 Type UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # lookup the object ids OBJECT: for my $Object (qw(Object1 Object2)) { # lookup the object id $Param{ $Object . 'ID' } = $Self->ObjectLookup( Name => $Param{$Object}, ); next OBJECT if $Param{ $Object . 'ID' }; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Invalid $Object is given!", ); return; } # lookup type id my $TypeID = $Self->TypeLookup( Name => $Param{Type}, UserID => $Param{UserID}, ); # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # get the existing link return if !$DBObject->Prepare( SQL => ' SELECT source_object_id, source_key, target_object_id, target_key, state_id FROM link_relation WHERE ( (source_object_id = ? AND source_key = ? AND target_object_id = ? AND target_key = ? ) OR ( source_object_id = ? AND source_key = ? AND target_object_id = ? AND target_key = ? ) ) AND type_id = ?', Bind => [ \$Param{Object1ID}, \$Param{Key1}, \$Param{Object2ID}, \$Param{Key2}, \$Param{Object2ID}, \$Param{Key2}, \$Param{Object1ID}, \$Param{Key1}, \$TypeID, ], Limit => 1, ); # fetch results my %Existing; while ( my @Row = $DBObject->FetchrowArray() ) { $Existing{SourceObjectID} = $Row[0]; $Existing{SourceKey} = $Row[1]; $Existing{TargetObjectID} = $Row[2]; $Existing{TargetKey} = $Row[3]; $Existing{StateID} = $Row[4]; } return 1 if !%Existing; # lookup the object names OBJECT: for my $Object (qw(SourceObject TargetObject)) { # lookup the object name $Existing{$Object} = $Self->ObjectLookup( ObjectID => $Existing{ $Object . 'ID' }, ); next OBJECT if $Existing{$Object}; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Invalid $Object is given!", ); return; } # lookup state $Existing{State} = $Self->StateLookup( StateID => $Existing{StateID}, ); # get backend of source object my $BackendSourceObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Existing{SourceObject} ); return if !$BackendSourceObject; # get backend of target object my $BackendTargetObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Existing{TargetObject} ); return if !$BackendTargetObject; # run pre event module of source object $BackendSourceObject->LinkDeletePre( Key => $Existing{SourceKey}, TargetObject => $Existing{TargetObject}, TargetKey => $Existing{TargetKey}, Type => $Param{Type}, State => $Existing{State}, UserID => $Param{UserID}, ); # run pre event module of target object $BackendTargetObject->LinkDeletePre( Key => $Existing{TargetKey}, SourceObject => $Existing{SourceObject}, SourceKey => $Existing{SourceKey}, Type => $Param{Type}, State => $Existing{State}, UserID => $Param{UserID}, ); # delete the link return if !$DBObject->Do( SQL => ' DELETE FROM link_relation WHERE ( ( source_object_id = ? AND source_key = ? AND target_object_id = ? AND target_key = ? ) OR ( source_object_id = ? AND source_key = ? AND target_object_id = ? AND target_key = ? ) ) AND type_id = ?', Bind => [ \$Param{Object1ID}, \$Param{Key1}, \$Param{Object2ID}, \$Param{Key2}, \$Param{Object2ID}, \$Param{Key2}, \$Param{Object1ID}, \$Param{Key1}, \$TypeID, ], ); # delete affected caches (both directions, all states, with/without type) my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); my %StateList = $Self->StateList(); for my $Direction (qw(Source Target)) { for my $DirectionNumber (qw(1 2)) { for my $StateID ( sort keys %StateList ) { my $CacheKey = 'Cache::LinkListRaw' . '::Direction' . $Direction . '::ObjectID' . $Param{ 'Object' . $DirectionNumber . 'ID' } . '::StateID' . $StateID; $CacheObject->Delete( Type => $Self->{CacheType}, Key => $CacheKey, ); } } } # run post event module of source object $BackendSourceObject->LinkDeletePost( Key => $Existing{SourceKey}, TargetObject => $Existing{TargetObject}, TargetKey => $Existing{TargetKey}, Type => $Param{Type}, State => $Existing{State}, UserID => $Param{UserID}, ); # run post event module of target object $BackendTargetObject->LinkDeletePost( Key => $Existing{TargetKey}, SourceObject => $Existing{SourceObject}, SourceKey => $Existing{SourceKey}, Type => $Param{Type}, State => $Existing{State}, UserID => $Param{UserID}, ); # Run event handlers. $Self->EventHandler( Event => 'LinkObjectLinkDelete', Data => { SourceObject => $Existing{SourceObject}, SourceKey => $Existing{SourceKey}, TargetObject => $Existing{TargetObject}, TargetKey => $Existing{TargetKey}, Type => $Param{Type}, State => $Existing{State}, }, UserID => $Param{UserID}, ); return 1; } =head2 LinkDeleteAll() delete all links of an object $True = $LinkObject->LinkDeleteAll( Object => 'Ticket', Key => '321', UserID => 1, ); =cut sub LinkDeleteAll { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object Key UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get state list my %StateList = $Self->StateList( Valid => 0, ); # delete all links STATE: for my $State ( values %StateList ) { # get link list my $LinkList = $Self->LinkList( Object => $Param{Object}, Key => $Param{Key}, State => $State, UserID => $Param{UserID}, ); next STATE if !$LinkList; next STATE if !%{$LinkList}; for my $Object ( sort keys %{$LinkList} ) { for my $LinkType ( sort keys %{ $LinkList->{$Object} } ) { # extract link type List my $LinkTypeList = $LinkList->{$Object}->{$LinkType}; for my $Direction ( sort keys %{$LinkTypeList} ) { # extract direction list my $DirectionList = $LinkList->{$Object}->{$LinkType}->{$Direction}; for my $ObjectKey ( sort keys %{$DirectionList} ) { # delete the link $Self->LinkDelete( Object1 => $Param{Object}, Key1 => $Param{Key}, Object2 => $Object, Key2 => $ObjectKey, Type => $LinkType, UserID => $Param{UserID}, ); } } } } } return 1; } =head2 LinkList() get all existing links for a given object Return $LinkList = { Ticket => { Normal => { Source => { 12 => 1, 212 => 1, 332 => 1, }, }, ParentChild => { Source => { 5 => 1, 9 => 1, }, Target => { 4 => 1, 8 => 1, 15 => 1, }, }, }, FAQ => { ParentChild => { Source => { 5 => 1, }, }, }, }; my $LinkList = $LinkObject->LinkList( Object => 'Ticket', Key => '321', Object2 => 'FAQ', # (optional) State => 'Valid', Type => 'ParentChild', # (optional) Direction => 'Target', # (optional) default Both (Source|Target|Both) UserID => 1, ); =cut sub LinkList { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object Key State UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # lookup object id my $ObjectID = $Self->ObjectLookup( Name => $Param{Object}, ); return if !$ObjectID; # lookup state id my $StateID = $Self->StateLookup( Name => $Param{State}, ); # add type id to SQL statement my $TypeID; if ( $Param{Type} ) { # lookup type id $TypeID = $Self->TypeLookup( Name => $Param{Type}, UserID => $Param{UserID}, ); } # get complete list for both directions (or only one if restricted) my $SourceLinks = {}; my $TargetLinks = {}; my $Direction = $Param{Direction} || 'Both'; if ( $Direction ne 'Target' ) { $SourceLinks = $Self->_LinkListRaw( Direction => 'Target', ObjectID => $ObjectID, Key => $Param{Key}, StateID => $StateID, TypeID => $TypeID, ); } $TargetLinks = $Self->_LinkListRaw( Direction => 'Source', ObjectID => $ObjectID, Key => $Param{Key}, StateID => $StateID, TypeID => $TypeID, ); # get names for used objects # consider restriction for Object2 my %ObjectNameLookup; OBJECTID: for my $ObjectID ( sort keys %{$SourceLinks}, sort keys %{$TargetLinks} ) { next OBJECTID if $ObjectNameLookup{$ObjectID}; # get object name my $ObjectName = $Self->ObjectLookup( ObjectID => $ObjectID, ); # add to lookup unless restricted next OBJECTID if $Param{Object2} && $Param{Object2} ne $ObjectName; $ObjectNameLookup{$ObjectID} = $ObjectName; } # shortcut: we have a restriction for Object2 but no matching links return {} if !%ObjectNameLookup; # get names and pointed info for used types my %TypeNameLookup; my %TypePointedLookup; for my $ObjectID ( sort keys %ObjectNameLookup ) { TYPEID: for my $TypeID ( sort keys %{ $SourceLinks->{$ObjectID} }, sort keys %{ $TargetLinks->{$ObjectID} } ) { next TYPEID if $TypeNameLookup{$TypeID}; # get type name my %TypeData = $Self->TypeGet( TypeID => $TypeID, ); $TypeNameLookup{$TypeID} = $TypeData{Name}; $TypePointedLookup{$TypeID} = $TypeData{Pointed}; } } # merge lists, move target keys to source keys for unpointed link types my %Links; for my $ObjectID ( sort keys %ObjectNameLookup ) { my $ObjectName = $ObjectNameLookup{$ObjectID}; TYPEID: for my $TypeID ( sort keys %TypeNameLookup ) { my $HaveSourceLinks = $SourceLinks->{$ObjectID}->{$TypeID} ? 1 : 0; my $HaveTargetLinks = $TargetLinks->{$ObjectID}->{$TypeID} ? 1 : 0; my $IsPointed = $TypePointedLookup{$TypeID}; my $TypeName = $TypeNameLookup{$TypeID}; # add target links as target if ( $Direction ne 'Source' && $HaveTargetLinks && $IsPointed ) { $Links{$ObjectName}->{$TypeName}->{Target} = $TargetLinks->{$ObjectID}->{$TypeID}; } next TYPEID if $Direction eq 'Target'; # add source links as source if ($HaveSourceLinks) { $Links{$ObjectName}->{$TypeName}->{Source} = $SourceLinks->{$ObjectID}->{$TypeID}; } # add target links as source for non-pointed links if ( !$IsPointed && $HaveTargetLinks ) { $Links{$ObjectName}->{$TypeName}->{Source} //= {}; $Links{$ObjectName}->{$TypeName}->{Source} = { %{ $Links{$ObjectName}->{$TypeName}->{Source} }, %{ $TargetLinks->{$ObjectID}->{$TypeID} }, }; } } } return \%Links; } =head2 LinkListWithData() get all existing links for a given object with data of the other objects Return $LinkList = { Ticket => { Normal => { Source => { 12 => $DataOfItem12, 212 => $DataOfItem212, 332 => $DataOfItem332, }, }, ParentChild => { Source => { 5 => $DataOfItem5, 9 => $DataOfItem9, }, Target => { 4 => $DataOfItem4, 8 => $DataOfItem8, 15 => $DataOfItem15, }, }, }, FAQ => { ParentChild => { Source => { 5 => $DataOfItem5, }, }, }, }; my $LinkList = $LinkObject->LinkListWithData( Object => 'Ticket', Key => '321', Object2 => 'FAQ', # (optional) State => 'Valid', Type => 'ParentChild', # (optional) Direction => 'Target', # (optional) default Both (Source|Target|Both) UserID => 1, ObjectParameters => { # (optional) backend specific flags Ticket => { IgnoreLinkedTicketStateTypes => 0|1, }, }, ); =cut sub LinkListWithData { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object Key State UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get the link list my $LinkList = $Self->LinkList(%Param); # check link list return if !$LinkList; return if ref $LinkList ne 'HASH'; # add data to hash OBJECT: for my $Object ( sort keys %{$LinkList} ) { # check if backend object can be loaded if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::LinkObject::' . $Object ) ) { delete $LinkList->{$Object}; next OBJECT; } # get backend object my $BackendObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Object ); # check backend object if ( !$BackendObject ) { delete $LinkList->{$Object}; next OBJECT; } my %ObjectParameters = (); if ( ref $Param{ObjectParameters} eq 'HASH' && ref $Param{ObjectParameters}->{$Object} eq 'HASH' ) { %ObjectParameters = %{ $Param{ObjectParameters}->{$Object} }; } # add backend data my $Success = $BackendObject->LinkListWithData( LinkList => $LinkList->{$Object}, UserID => $Param{UserID}, %ObjectParameters, ); next OBJECT if $Success; delete $LinkList->{$Object}; } # clean the hash OBJECT: for my $Object ( sort keys %{$LinkList} ) { LINKTYPE: for my $LinkType ( sort keys %{ $LinkList->{$Object} } ) { DIRECTION: for my $Direction ( sort keys %{ $LinkList->{$Object}->{$LinkType} } ) { next DIRECTION if %{ $LinkList->{$Object}->{$LinkType}->{$Direction} }; delete $LinkList->{$Object}->{$LinkType}->{$Direction}; } next LINKTYPE if %{ $LinkList->{$Object}->{$LinkType} }; delete $LinkList->{$Object}->{$LinkType}; } next OBJECT if %{ $LinkList->{$Object} }; delete $LinkList->{$Object}; } return $LinkList; } =head2 LinkKeyList() return a hash with all existing links of a given object Return %LinkKeyList = ( 5 => 1, 9 => 1, 12 => 1, 212 => 1, 332 => 1, ); my %LinkKeyList = $LinkObject->LinkKeyList( Object1 => 'Ticket', Key1 => '321', Object2 => 'FAQ', State => 'Valid', Type => 'ParentChild', # (optional) Direction => 'Target', # (optional) default Both (Source|Target|Both) UserID => 1, ); =cut sub LinkKeyList { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object1 Key1 Object2 State UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get the link list my $LinkList = $Self->LinkList( %Param, Object => $Param{Object1}, Key => $Param{Key1}, ); # check link list return if !$LinkList; return if ref $LinkList ne 'HASH'; # extract typelist my $TypeList = $LinkList->{ $Param{Object2} }; # add data to hash my %LinkKeyList; for my $Type ( sort keys %{$TypeList} ) { # extract direction list my $DirectionList = $TypeList->{$Type}; for my $Direction ( sort keys %{$DirectionList} ) { for my $Key ( sort keys %{ $DirectionList->{$Direction} } ) { # add key to list $LinkKeyList{$Key} = $DirectionList->{$Direction}->{$Key}; } } } return %LinkKeyList; } =head2 LinkKeyListWithData() return a hash with all existing links of a given object Return %LinkKeyList = ( 5 => $DataOfItem5, 9 => $DataOfItem9, 12 => $DataOfItem12, 212 => $DataOfItem212, 332 => $DataOfItem332, ); my %LinkKeyList = $LinkObject->LinkKeyListWithData( Object1 => 'Ticket', Key1 => '321', Object2 => 'FAQ', State => 'Valid', Type => 'ParentChild', # (optional) Direction => 'Target', # (optional) default Both (Source|Target|Both) UserID => 1, ); =cut sub LinkKeyListWithData { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object1 Key1 Object2 State UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get the link list my $LinkList = $Self->LinkListWithData( %Param, Object => $Param{Object1}, Key => $Param{Key1}, ); # check link list return if !$LinkList; return if ref $LinkList ne 'HASH'; # extract typelist my $TypeList = $LinkList->{ $Param{Object2} }; # add data to hash my %LinkKeyList; for my $Type ( sort keys %{$TypeList} ) { # extract direction list my $DirectionList = $TypeList->{$Type}; for my $Direction ( sort keys %{$DirectionList} ) { for my $Key ( sort keys %{ $DirectionList->{$Direction} } ) { # add key to list $LinkKeyList{$Key} = $DirectionList->{$Direction}->{$Key}; } } } return %LinkKeyList; } =head2 ObjectLookup() lookup a link object $ObjectID = $LinkObject->ObjectLookup( Name => 'Ticket', ); or $Name = $LinkObject->ObjectLookup( ObjectID => 12, ); =cut sub ObjectLookup { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{ObjectID} && !$Param{Name} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need ObjectID or Name!', ); return; } if ( $Param{ObjectID} ) { # check cache my $CacheKey = 'ObjectLookup::ObjectID::' . $Param{ObjectID}; my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); return $Cache if $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # ask the database return if !$DBObject->Prepare( SQL => ' SELECT name FROM link_object WHERE id = ?', Bind => [ \$Param{ObjectID} ], Limit => 1, ); # fetch the result my $Name; while ( my @Row = $DBObject->FetchrowArray() ) { $Name = $Row[0]; } # check the name if ( !$Name ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Link object id '$Param{ObjectID}' not found in the database!", ); return; } # set cache $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, Value => $Name, ); return $Name; } else { # check cache my $CacheKey = 'ObjectLookup::Name::' . $Param{Name}; my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); return $Cache if $Cache; # get needed object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); # investigate the object id my $ObjectID; TRY: for my $Try ( 1 .. 3 ) { # ask the database return if !$DBObject->Prepare( SQL => ' SELECT id FROM link_object WHERE name = ?', Bind => [ \$Param{Name} ], Limit => 1, ); # fetch the result while ( my @Row = $DBObject->FetchrowArray() ) { $ObjectID = $Row[0]; } last TRY if $ObjectID; # cleanup the given name $CheckItemObject->StringClean( StringRef => \$Param{Name}, ); # check if name is valid if ( !$Param{Name} || $Param{Name} =~ m{ :: }xms || $Param{Name} =~ m{ \s }xms ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Invalid object name '$Param{Name}' is given!", ); return; } next TRY if $Try == 1; # insert the new object return if !$DBObject->Do( SQL => 'INSERT INTO link_object (name) VALUES (?)', Bind => [ \$Param{Name} ], ); } # set cache $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, Value => $ObjectID, ); return $ObjectID; } } =head2 TypeLookup() lookup a link type $TypeID = $LinkObject->TypeLookup( Name => 'Normal', UserID => 1, ); or $Name = $LinkObject->TypeLookup( TypeID => 56, UserID => 1, ); =cut sub TypeLookup { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{TypeID} && !$Param{Name} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need TypeID or Name!', ); return; } # check needed stuff if ( !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need UserID!' ); return; } if ( $Param{TypeID} ) { # check cache my $CacheKey = 'TypeLookup::TypeID::' . $Param{TypeID}; my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); return $Cache if $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # ask the database return if !$DBObject->Prepare( SQL => 'SELECT name FROM link_type WHERE id = ?', Bind => [ \$Param{TypeID} ], Limit => 1, ); # fetch the result my $Name; while ( my @Row = $DBObject->FetchrowArray() ) { $Name = $Row[0]; } # check the name if ( !$Name ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Link type id '$Param{TypeID}' not found in the database!", ); return; } # set cache $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, Value => $Name, ); return $Name; } else { # get check item object my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); # cleanup the given name $CheckItemObject->StringClean( StringRef => \$Param{Name}, ); # check cache my $CacheKey = 'TypeLookup::Name::' . $Param{Name}; my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); return $Cache if $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # investigate the type id my $TypeID; TRY: for my $Try ( 1 .. 2 ) { # ask the database return if !$DBObject->Prepare( SQL => 'SELECT id FROM link_type WHERE name = ?', Bind => [ \$Param{Name} ], Limit => 1, ); # fetch the result while ( my @Row = $DBObject->FetchrowArray() ) { $TypeID = $Row[0]; } last TRY if $TypeID; # check if name is valid if ( !$Param{Name} || $Param{Name} =~ m{ :: }xms || $Param{Name} =~ m{ \s }xms ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Invalid type name '$Param{Name}' is given!", ); return; } # insert the new type return if !$DBObject->Do( SQL => ' INSERT INTO link_type (name, valid_id, create_time, create_by, change_time, change_by) VALUES (?, 1, current_timestamp, ?, current_timestamp, ?)', Bind => [ \$Param{Name}, \$Param{UserID}, \$Param{UserID} ], ); } # check the type id if ( !$TypeID ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Link type '$Param{Name}' not found in the database!", ); return; } # set cache $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, Value => $TypeID, ); return $TypeID; } } =head2 TypeGet() get a link type Return $TypeData{TypeID} $TypeData{Name} $TypeData{SourceName} $TypeData{TargetName} $TypeData{Pointed} $TypeData{CreateTime} $TypeData{CreateBy} $TypeData{ChangeTime} $TypeData{ChangeBy} %TypeData = $LinkObject->TypeGet( TypeID => 444, ); =cut sub TypeGet { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(TypeID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # check cache my $CacheKey = 'TypeGet::TypeID::' . $Param{TypeID}; my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); return %{$Cache} if $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # ask the database return if !$DBObject->Prepare( SQL => ' SELECT id, name, create_time, create_by, change_time, change_by FROM link_type WHERE id = ?', Bind => [ \$Param{TypeID} ], Limit => 1, ); # fetch the result my %Type; while ( my @Row = $DBObject->FetchrowArray() ) { $Type{TypeID} = $Row[0]; $Type{Name} = $Row[1]; $Type{CreateTime} = $Row[2]; $Type{CreateBy} = $Row[3]; $Type{ChangeTime} = $Row[4]; $Type{ChangeBy} = $Row[5]; } # get config of all types my $ConfiguredTypes = $Kernel::OM->Get('Kernel::Config')->Get('LinkObject::Type'); # check the config if ( !$ConfiguredTypes->{ $Type{Name} } ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Linktype '$Type{Name}' does not exist!", ); return; } # add source and target name $Type{SourceName} = $ConfiguredTypes->{ $Type{Name} }->{SourceName} || ''; $Type{TargetName} = $ConfiguredTypes->{ $Type{Name} }->{TargetName} || ''; # get check item object my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); # clean the names ARGUMENT: for my $Argument (qw(SourceName TargetName)) { $CheckItemObject->StringClean( StringRef => \$Type{$Argument}, RemoveAllNewlines => 1, RemoveAllTabs => 1, ); next ARGUMENT if $Type{$Argument}; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "The $Argument '$Type{$Argument}' is invalid in SysConfig (LinkObject::Type)!", ); return; } # add pointed value $Type{Pointed} = $Type{SourceName} ne $Type{TargetName} ? 1 : 0; # set cache $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, Value => \%Type, ); return %Type; } =head2 TypeList() return a 2 dimensional hash list of all valid link types Return $TypeList{ Normal => { SourceName => 'Normal', TargetName => 'Normal', }, ParentChild => { SourceName => 'Parent', TargetName => 'Child', }, } my %TypeList = $LinkObject->TypeList(); =cut sub TypeList { my ( $Self, %Param ) = @_; # get type list my $TypeListRef = $Kernel::OM->Get('Kernel::Config')->Get('LinkObject::Type') || {}; my %TypeList = %{$TypeListRef}; # get check item object my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); # prepare the type list TYPE: for my $Type ( sort keys %TypeList ) { # check the source and target name ARGUMENT: for my $Argument (qw(SourceName TargetName)) { # set empty string as default value $TypeList{$Type}{$Argument} ||= ''; # clean the argument $CheckItemObject->StringClean( StringRef => \$TypeList{$Type}{$Argument}, RemoveAllNewlines => 1, RemoveAllTabs => 1, ); next ARGUMENT if $TypeList{$Type}{$Argument}; # remove invalid link type from list delete $TypeList{$Type}; next TYPE; } } return %TypeList; } =head2 TypeGroupList() return a 2 dimensional hash list of all type groups Return %TypeGroupList = ( 001 => [ 'Normal', 'ParentChild', ], 002 => [ 'Normal', 'DependsOn', ], 003 => [ 'ParentChild', 'RelevantTo', ], ); my %TypeGroupList = $LinkObject->TypeGroupList(); =cut sub TypeGroupList { my ( $Self, %Param ) = @_; # get possible type groups my $TypeGroupListRef = $Kernel::OM->Get('Kernel::Config')->Get('LinkObject::TypeGroup') || {}; my %TypeGroupList = %{$TypeGroupListRef}; # get check item object my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem'); # prepare the possible link list TYPEGROUP: for my $TypeGroup ( sort keys %TypeGroupList ) { # check the types TYPE: for my $Type ( @{ $TypeGroupList{$TypeGroup} } ) { # set empty string as default value $Type ||= ''; # trim the argument $CheckItemObject->StringClean( StringRef => \$Type, ); next TYPE if $Type && $Type !~ m{ :: }xms && $Type !~ m{ \s }xms; # log the error $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "The Argument '$Type' is invalid in SysConfig (LinkObject::TypeGroup)!", ); # remove entry from list if it is invalid delete $TypeGroupList{$TypeGroup}; next TYPEGROUP; } } # get type list my %TypeList = $Self->TypeList(); # check types TYPEGROUP: for my $TypeGroup ( sort keys %TypeGroupList ) { # check the types TYPE: for my $Type ( @{ $TypeGroupList{$TypeGroup} } ) { # set empty string as default value $Type ||= ''; next TYPE if $TypeList{$Type}; # log the error $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "The LinkType '$Type' is invalid in SysConfig (LinkObject::TypeGroup)!", ); # remove entry from list if type doesn't exist delete $TypeGroupList{$TypeGroup}; next TYPEGROUP; } } return %TypeGroupList; } =head2 PossibleType() return true if both types are NOT together in a type group my $True = $LinkObject->PossibleType( Type1 => 'Normal', Type2 => 'ParentChild', ); =cut sub PossibleType { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Type1 Type2)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get type group list my %TypeGroupList = $Self->TypeGroupList(); # check all type groups TYPEGROUP: for my $TypeGroup ( sort keys %TypeGroupList ) { my %TypeList = map { $_ => 1 } @{ $TypeGroupList{$TypeGroup} }; return if $TypeList{ $Param{Type1} } && $TypeList{ $Param{Type2} }; } return 1; } =head2 StateLookup() lookup a link state $StateID = $LinkObject->StateLookup( Name => 'Valid', ); or $Name = $LinkObject->StateLookup( StateID => 56, ); =cut sub StateLookup { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{StateID} && !$Param{Name} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need StateID or Name!', ); return; } if ( $Param{StateID} ) { # check cache my $CacheKey = 'StateLookup::StateID::' . $Param{StateID}; my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); return $Cache if $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # ask the database return if !$DBObject->Prepare( SQL => ' SELECT name FROM link_state WHERE id = ?', Bind => [ \$Param{StateID} ], Limit => 1, ); # fetch the result my $Name; while ( my @Row = $DBObject->FetchrowArray() ) { $Name = $Row[0]; } # check the name if ( !$Name ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Link state id '$Param{StateID}' not found in the database!", ); return; } # set cache $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, Value => $Name, ); return $Name; } else { # check cache my $CacheKey = 'StateLookup::Name::' . $Param{Name}; my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); return $Cache if $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # ask the database return if !$DBObject->Prepare( SQL => ' SELECT id FROM link_state WHERE name = ?', Bind => [ \$Param{Name} ], Limit => 1, ); # fetch the result my $StateID; while ( my @Row = $DBObject->FetchrowArray() ) { $StateID = $Row[0]; } # check the state id if ( !$StateID ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Link state '$Param{Name}' not found in the database!", ); return; } # set cache $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, Value => $StateID, ); return $StateID; } } =head2 StateList() return a hash list of all valid link states Return $StateList{ 4 => 'Valid', 8 => 'Temporary', } my %StateList = $LinkObject->StateList( Valid => 0, # (optional) default 1 (0|1) ); =cut sub StateList { my ( $Self, %Param ) = @_; # set valid param if ( !defined $Param{Valid} ) { $Param{Valid} = 1; } # add valid part my $SQLWhere = ''; if ( $Param{Valid} ) { # create the valid id string my $ValidIDs = join ', ', $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet(); $SQLWhere = "WHERE valid_id IN ( $ValidIDs )"; } # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # ask database return if !$DBObject->Prepare( SQL => "SELECT id, name FROM link_state $SQLWhere", ); # fetch the result my %StateList; while ( my @Row = $DBObject->FetchrowArray() ) { $StateList{ $Row[0] } = $Row[1]; } return %StateList; } =head2 ObjectPermission() checks read permission for a given object and UserID. $Permission = $LinkObject->ObjectPermission( Object => 'Ticket', Key => 123, UserID => 1, ); =cut sub ObjectPermission { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object Key UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get backend object my $BackendObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{Object} ); return if !$BackendObject; return 1 if !$BackendObject->can('ObjectPermission'); return $BackendObject->ObjectPermission( %Param, ); } =head2 ObjectDescriptionGet() return a hash of object descriptions Return %Description = ( Normal => '', Long => '', ); %Description = $LinkObject->ObjectDescriptionGet( Object => 'Ticket', Key => 123, UserID => 1, ); =cut sub ObjectDescriptionGet { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object Key UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get backend object my $BackendObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{Object} ); return if !$BackendObject; # get object description my %Description = $BackendObject->ObjectDescriptionGet( %Param, ); return %Description; } =head2 ObjectSearch() return a hash reference of the search results. Returns: $ObjectList = { Ticket => { NOTLINKED => { Source => { 12 => $DataOfItem12, 212 => $DataOfItem212, 332 => $DataOfItem332, }, }, }, }; $ObjectList = $LinkObject->ObjectSearch( Object => 'ITSMConfigItem', SubObject => 'Computer' # (optional) SearchParams => $HashRef, # (optional) UserID => 1, ); =cut sub ObjectSearch { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Object UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # get backend object my $BackendObject = $Kernel::OM->Get( 'Kernel::System::LinkObject::' . $Param{Object} ); return if !$BackendObject; # search objects my $SearchList = $BackendObject->ObjectSearch( %Param, ); my %ObjectList; $ObjectList{ $Param{Object} } = $SearchList; return \%ObjectList; } sub _LinkListRaw { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(Direction ObjectID Key StateID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # check cache my $CacheKey = 'Cache::LinkListRaw' . '::Direction' . $Param{Direction} . '::ObjectID' . $Param{ObjectID} . '::StateID' . $Param{StateID}; my $CachedLinks = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); my @Links; if ( ref $CachedLinks eq 'ARRAY' ) { @Links = @{$CachedLinks}; } else { # prepare SQL statement my $TypeSQL = ''; my @Bind = ( \$Param{ObjectID}, \$Param{StateID} ); # get fields based on type my $SQL; if ( $Param{Direction} eq 'Source' ) { $SQL = 'SELECT target_object_id, target_key, type_id, source_key' . ' FROM link_relation' . ' WHERE source_object_id = ?'; } else { $SQL = 'SELECT source_object_id, source_key, type_id, target_key' . ' FROM link_relation' . ' WHERE target_object_id = ?'; } $SQL .= ' AND state_id = ?' . $TypeSQL; # get all links for object/state/type (for better caching) my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); return if !$DBObject->Prepare( SQL => $SQL, Bind => \@Bind, ); # fetch results while ( my @Row = $DBObject->FetchrowArray() ) { push @Links, { ObjectID => $Row[0], ResponseKey => $Row[1], TypeID => $Row[2], RequestKey => $Row[3], }; } # set cache $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, # make a local copy of the data to avoid it being altered in-memory later Value => [@Links], ); } # fill result hash and if necessary filter by specified key and/or type id my %List; LINK: for my $Link (@Links) { next LINK if $Link->{RequestKey} ne $Param{Key}; next LINK if $Param{TypeID} && $Link->{TypeID} ne $Param{TypeID}; $List{ $Link->{ObjectID} }->{ $Link->{TypeID} }->{ $Link->{ResponseKey} } = 1; } return \%List; } 1; =head1 TERMS AND CONDITIONS This software is part of the OTRS project (L). This software comes with ABSOLUTELY NO WARRANTY. For details, see the enclosed file COPYING for license information (GPL). If you did not receive this file, see L. =cut