# --
# 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