Skip to content

Commit

Permalink
Issue #4182: Add auto-link support to BaseReference DF Driver
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelThumes authored and svenoe committed Feb 24, 2025
1 parent 3d775e6 commit 7ced1e0
Showing 1 changed file with 260 additions and 17 deletions.
277 changes: 260 additions & 17 deletions Kernel/System/DynamicField/Driver/BaseReference.pm
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ use Kernel::System::VariableCheck qw(IsArrayRefWithData IsStringWithData);

our @ObjectDependencies = (
'Kernel::Config',
'Kernel::Language',
'Kernel::Output::HTML::Layout',
'Kernel::System::DB',
'Kernel::System::DynamicFieldValue',
'Kernel::System::DynamicField::Backend',
'Kernel::System::Log',
'Kernel::System::LinkObject',
'Kernel::System::Web::FormCache',
);

Expand All @@ -58,6 +61,30 @@ Please look there for a detailed reference of the functions.
=cut

sub ValueSet {
my ( $Self, %Param ) = @_;

my $Result = $Self->SUPER::ValueSet(%Param);

if ($Result) {

# optional classic LinkOBject links
my $DynamicFieldConfig = $Param{DynamicFieldConfig};

my $ValueType = ref( $Param{Value} );
my $Value = $ValueType && $ValueType eq 'ARRAY' ? $Param{Value}->[0] : $Param{Value};

$Self->_CreateAutoLinkObjectLink(
UserID => $Param{UserID},
ObjectID => $Param{ObjectID},
DynamicField => $DynamicFieldConfig,
Value => $Value,
);
}

return $Result;
}

sub ValueValidate {
my ( $Self, %Param ) = @_;

Expand Down Expand Up @@ -159,25 +186,13 @@ sub SearchSQLGet {
}

my $DBObject = $Kernel::OM->Get('Kernel::System::DB');

# TODO: this should be changed to bind variables
my $SQL;
if ( $Self->{TableAttribute} eq 'value_int' ) {
$SQL = " $Param{TableAlias}.$Self->{TableAttribute} $Operators{ $Param{Operator} } $Param{SearchTerm}";
my $Lower = '';
if ( $DBObject->GetDatabaseFunction('CaseSensitive') ) {
$Lower = 'LOWER';
}
elsif ( $Self->{TableAttribute} eq 'value_text' ) {
my $Lower = '';
if ( $DBObject->GetDatabaseFunction('CaseSensitive') ) {
$Lower = 'LOWER';
}

$SQL = " $Lower($Param{TableAlias}.$Self->{TableAttribute}) $Operators{ $Param{Operator} } ";
$SQL .= "$Lower('" . $DBObject->Quote( $Param{SearchTerm} ) . "') ";
}
else {
$SQL = " $Param{TableAlias}.$Self->{TableAttribute} $Operators{ $Param{Operator} } '";
$SQL .= $DBObject->Quote( $Param{SearchTerm} ) . "' ";
}
my $SQL = " $Lower($Param{TableAlias}.$Self->{TableAttribute}) $Operators{ $Param{Operator} } ";
$SQL .= "$Lower('" . $DBObject->Quote( $Param{SearchTerm} ) . "') ";

return $SQL;
}
Expand Down Expand Up @@ -994,6 +1009,84 @@ sub GetFieldTypeSettings {
};
}

my $LinkObject = $Kernel::OM->Get('Kernel::System::LinkObject');
my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
my $ReferencingObjectType = $Param{ObjectType};

# Create the selectable type list from the possible types from the SysConfig.
my @SelectionData;

# get possible types list,
# actually the order of Object1 and Object2 is not relevant
my $Object1 = $ReferencingObjectType =~ s/^ITSMConfigItemVersion$/ITSMConfigItem/r;
my $Object2 = $Self->{ReferencedObjectType} =~ s/^ITSMConfigItemVersion$/ITSMConfigItem/r;
my %PossibleTypesList = $LinkObject->PossibleTypesList(
Object1 => $Object1, # the entity that holds the Reference dynamic field can be a config item or a ticket
Object2 => $Object2, # the referenced object
);

# only show selection if there are any valid link types
if ( scalar keys %PossibleTypesList != 0 ) {

POSSIBLETYPE:
for my $PossibleType ( sort { lc $a cmp lc $b } keys %PossibleTypesList ) {

# look up type id,
# insert the name into the table link_type if it does not exist yet
my $TypeID = $LinkObject->TypeLookup(
Name => $PossibleType,
UserID => 1, # TODO: get the actual id of the current user
);

# get type
my %Type = $LinkObject->TypeGet(
TypeID => $TypeID,
UserID => $Self->{UserID},
);

push @SelectionData,
{
Key => $PossibleType,
Value => "Source -$Type{SourceName}\-> Target ($Type{TargetName})",
};
}

push @GenericSettings,
{
InputType => 'Selection',
ConfigParamName => $Self->{ReferencedObjectType} =~ '^ITSMConfigItem' ? 'LinkType' : 'LinkObjectForReferenceType',
Label => Translatable('Link type'),
Explanation => Translatable('Select the link type.'),
SelectionData => \@SelectionData,
PossibleNone => 1,
};

my @SelectionDirectionData = (
{
Key => 'ReferencingIsSource',
Value => Translatable('Forwards: Referencing (Source) -> Referenced (Target)'),
},
{
Key => 'ReferencingIsTarget',
Value => Translatable('Backwards: Referenced (Source) -> Referencing (Target)'),
},
);

push @GenericSettings,
{
ConfigParamName => 'LinkDirection',
Label => Translatable('Link Direction'),
Explanation =>
Translatable('The referencing object is the one containing this dynamic field, the referenced object is the one selected as value of the dynamic field.'),
InputType => 'Selection',
SelectionData => \@SelectionDirectionData,
DefaultKey => 'ReferencingIsSource',
PossibleNone => 0,
};
}

# EO ITSMconfigurationmanagement

return @GenericSettings;
}

Expand Down Expand Up @@ -1140,4 +1233,154 @@ sub GetFieldState {
return %Return;
}

sub _CreateAutoLinkObjectLink {

my ( $Self, %Param ) = @_;

my $LinkObject = $Kernel::OM->Get('Kernel::System::LinkObject');

for my $Needed (qw(DynamicField ObjectID UserID)) {
if ( !$Param{$Needed} ) {

$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => "Missing Input Param '$Needed' needed to create Link\n",
);
return;
}
}

return unless $Param{Value};

my $DynamicField = $Param{DynamicField};
my $LinkDirection = $DynamicField->{Config}->{LinkDirection};
my $LinkType = $DynamicField->{Config}->{LinkObjectForReferenceType} || $DynamicField->{Config}->{LinkType};

# if linking disabled in DF config, do nothing
return unless $LinkType;

# object types for linking
my $TargetObject = $DynamicField->{FieldType};
$TargetObject =~ s/^ConfigItem/ITSMConfigItem/;
$TargetObject =~ s/^ITSMConfigItemVersion/ITSMConfigItem/;

my $SourceObject = $DynamicField->{ObjectType};
$SourceObject =~ s/^ConfigItem/ITSMConfigItem/;
$SourceObject =~ s/^ITSMConfigItemVersion/ITSMConfigItem/;

# object key IDs for linking
my $TargetKey = $Self->_GetEntityIDForLinking(
LinkKey => 'Target',
TypeName => $DynamicField->{FieldType},
ID => $Param{Value},
);

my $SourceKey = $Self->_GetEntityIDForLinking(
LinkKey => 'Source',
TypeName => $DynamicField->{ObjectType},
ID => $Param{ObjectID},
);

# link direction
if ( $LinkDirection ne 'ReferencingIsSource' ) {

# swap variables around
( $SourceObject, $TargetObject ) = ( $TargetObject, $SourceObject );
( $SourceKey, $TargetKey ) = ( $TargetKey, $SourceKey );
}

# check if link already exists

my $LinkList = $LinkObject->LinkList(
Object => $SourceObject,
Key => $SourceKey,
Object2 => $TargetObject,
State => 'Valid',
UserID => $Param{UserID},
);

my $Links = $LinkList->{$SourceObject}->{$LinkType};
for my $LinkTypeKey ( keys %$Links ) {

my $References = $Links->{$LinkTypeKey};
for my $Key ( keys %$References ) {

return unless $Key != $SourceKey;
}
}

# and the other way round
$Links = $LinkList->{$TargetObject}->{$LinkType};
for my $LinkTypeKey ( keys %$Links ) {

my $References = $Links->{$LinkTypeKey};
for my $Key ( keys %$References ) {

return unless $Key != $TargetKey;
}
}

# do the actual linking
my $Success = $LinkObject->LinkAdd(
SourceObject => $SourceObject, # eg 'Ticket',
SourceKey => $SourceKey,
TargetObject => $TargetObject, # eg 'ITSMConfigItem',
TargetKey => $TargetKey,
Type => $LinkType, # eg 'RelevantTo',
State => 'Valid',
UserID => $Param{UserID},
);

return 1 if $Success;

$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Unable to create Link $SourceObject ($SourceKey) -- $LinkType -> $TargetObject $TargetKey\n",
);

return;
}

sub _GetEntityIDForLinking {

my ( $Self, %Param ) = @_;

my $ID = $Param{ID};
my $TypeName = $Param{TypeName};
my $LinkKey = $Param{LinkKey};

# determine name for the K/S/DynamicField/ObjectType/* ObjectTypeHandler class
# upgrade ConfigItem typenames to long form (starting with ITSM*)
# and treat ConfigItemVersion like ConfigItem
$TypeName =~ s/^ConfigItem/ITSMConfigItem/;
$TypeName =~ s/^ITSMConfigItemVersion/ITSMConfigItem/;

# BackendObject has all the ObjectType Handlers loaded as properties
my $BackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');

# determine the property name for our typename
my $BackendTypeObjectKey = "DynamicField" . $TypeName . 'HandlerObject';

# if there is a TypeObject handler object ...
if ( exists $BackendObject->{$BackendTypeObjectKey} ) {

my $BackendTypeObject = $BackendObject->{$BackendTypeObjectKey};

# ask TypeObject if it supports linking ID resolution
my $HasGetEntityIDforLinking = $BackendTypeObject->can('GetEntityIDforLinking');

if ($HasGetEntityIDforLinking) {

return $BackendTypeObject->GetEntityIDforLinking(
ID => $ID,
LinkKey => $LinkKey,
TypeName => $Param{TypeName},
);
}
}

# otherwise ID stays as is
return $ID;
}

1;

0 comments on commit 7ced1e0

Please sign in to comment.