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