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::DynamicField;
10
11use strict;
12use warnings;
13
14use parent qw(Kernel::System::EventHandler);
15
16use Kernel::System::VariableCheck qw(:all);
17
18our @ObjectDependencies = (
19    'Kernel::Config',
20    'Kernel::System::Cache',
21    'Kernel::System::DB',
22    'Kernel::System::Log',
23    'Kernel::System::Valid',
24    'Kernel::System::YAML',
25);
26
27=head1 NAME
28
29Kernel::System::DynamicField
30
31=head1 DESCRIPTION
32
33DynamicFields backend
34
35=head1 PUBLIC INTERFACE
36
37=head2 new()
38
39create a DynamicField object. Do not use it directly, instead use:
40
41    my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
42
43=cut
44
45sub new {
46    my ( $Type, %Param ) = @_;
47
48    # allocate new hash for object
49    my $Self = {};
50    bless( $Self, $Type );
51
52    # get the cache TTL (in seconds)
53    $Self->{CacheTTL} = $Kernel::OM->Get('Kernel::Config')->Get('DynamicField::CacheTTL') || 3600;
54
55    # set lower if database is case sensitive
56    $Self->{Lower} = '';
57    if ( $Kernel::OM->Get('Kernel::System::DB')->GetDatabaseFunction('CaseSensitive') ) {
58        $Self->{Lower} = 'LOWER';
59    }
60
61    # init of event handler
62    $Self->EventHandlerInit(
63        Config => 'DynamicField::EventModulePost',
64    );
65
66    return $Self;
67}
68
69=head2 DynamicFieldAdd()
70
71add new Dynamic Field config
72
73returns id of new Dynamic field if successful or undef otherwise
74
75    my $ID = $DynamicFieldObject->DynamicFieldAdd(
76        InternalField => 0,             # optional, 0 or 1, internal fields are protected
77        Name        => 'NameForField',  # mandatory
78        Label       => 'a description', # mandatory, label to show
79        FieldOrder  => 123,             # mandatory, display order
80        FieldType   => 'Text',          # mandatory, selects the DF backend to use for this field
81        ObjectType  => 'Article',       # this controls which object the dynamic field links to
82                                        # allow only lowercase letters
83        Config      => $ConfigHashRef,  # it is stored on YAML format
84                                        # to individual articles, otherwise to tickets
85        Reorder     => 1,               # or 0, to trigger reorder function, default 1
86        ValidID     => 1,
87        UserID      => 123,
88    );
89
90Returns:
91
92    $ID = 567;
93
94=cut
95
96sub DynamicFieldAdd {
97    my ( $Self, %Param ) = @_;
98
99    # check needed stuff
100    for my $Key (qw(Name Label FieldOrder FieldType ObjectType Config ValidID UserID)) {
101        if ( !$Param{$Key} ) {
102            $Kernel::OM->Get('Kernel::System::Log')->Log(
103                Priority => 'error',
104                Message  => "Need $Key!"
105            );
106            return;
107        }
108    }
109
110    # check needed structure for some fields
111    if ( $Param{Name} !~ m{ \A [a-zA-Z\d]+ \z }xms ) {
112        $Kernel::OM->Get('Kernel::System::Log')->Log(
113            Priority => 'error',
114            Message  => "Not valid letters on Name:$Param{Name}!"
115        );
116        return;
117    }
118
119    # get database object
120    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
121
122    # check if Name already exists
123    return if !$DBObject->Prepare(
124        SQL   => "SELECT id FROM dynamic_field WHERE $Self->{Lower}(name) = $Self->{Lower}(?)",
125        Bind  => [ \$Param{Name} ],
126        Limit => 1,
127    );
128
129    my $NameExists;
130    while ( my @Data = $DBObject->FetchrowArray() ) {
131        $NameExists = 1;
132    }
133
134    if ($NameExists) {
135        $Kernel::OM->Get('Kernel::System::Log')->Log(
136            Priority => 'error',
137            Message  => "A dynamic field with the name '$Param{Name}' already exists.",
138        );
139        return;
140    }
141
142    if ( $Param{FieldOrder} !~ m{ \A [\d]+ \z }xms ) {
143        $Kernel::OM->Get('Kernel::System::Log')->Log(
144            Priority => 'error',
145            Message  => "Not valid number on FieldOrder:$Param{FieldOrder}!"
146        );
147        return;
148    }
149
150    # dump config as string
151    my $Config = $Kernel::OM->Get('Kernel::System::YAML')->Dump( Data => $Param{Config} );
152
153    # Make sure the resulting string has the UTF-8 flag. YAML only sets it if
154    #   part of the data already had it.
155    utf8::upgrade($Config);
156
157    my $InternalField = $Param{InternalField} ? 1 : 0;
158
159    # sql
160    return if !$DBObject->Do(
161        SQL =>
162            'INSERT INTO dynamic_field (internal_field, name, label, field_Order, field_type, object_type,'
163            .
164            ' config, valid_id, create_time, create_by, change_time, change_by)' .
165            ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)',
166        Bind => [
167            \$InternalField, \$Param{Name}, \$Param{Label}, \$Param{FieldOrder}, \$Param{FieldType},
168            \$Param{ObjectType}, \$Config, \$Param{ValidID}, \$Param{UserID}, \$Param{UserID},
169        ],
170    );
171
172    # get cache object
173    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
174
175    # delete cache
176    $CacheObject->CleanUp(
177        Type => 'DynamicField',
178    );
179    $CacheObject->CleanUp(
180        Type => 'DynamicFieldValue',
181    );
182
183    my $DynamicField = $Self->DynamicFieldGet(
184        Name => $Param{Name},
185    );
186
187    return if !$DynamicField->{ID};
188
189    # trigger event
190    $Self->EventHandler(
191        Event => 'DynamicFieldAdd',
192        Data  => {
193            NewData => $DynamicField,
194        },
195        UserID => $Param{UserID},
196    );
197
198    if ( !exists $Param{Reorder} || $Param{Reorder} ) {
199
200        # re-order field list
201        $Self->_DynamicFieldReorder(
202            ID         => $DynamicField->{ID},
203            FieldOrder => $DynamicField->{FieldOrder},
204            Mode       => 'Add',
205        );
206    }
207
208    return $DynamicField->{ID};
209}
210
211=head2 DynamicFieldGet()
212
213get Dynamic Field attributes
214
215    my $DynamicField = $DynamicFieldObject->DynamicFieldGet(
216        ID   => 123,             # ID or Name must be provided
217        Name => 'DynamicField',
218    );
219
220Returns:
221
222    $DynamicField = {
223        ID            => 123,
224        InternalField => 0,
225        Name          => 'NameForField',
226        Label         => 'The label to show',
227        FieldOrder    => 123,
228        FieldType     => 'Text',
229        ObjectType    => 'Article',
230        Config        => $ConfigHashRef,
231        ValidID       => 1,
232        CreateTime    => '2011-02-08 15:08:00',
233        ChangeTime    => '2011-06-11 17:22:00',
234    };
235
236=cut
237
238sub DynamicFieldGet {
239    my ( $Self, %Param ) = @_;
240
241    # check needed stuff
242    if ( !$Param{ID} && !$Param{Name} ) {
243        $Kernel::OM->Get('Kernel::System::Log')->Log(
244            Priority => 'error',
245            Message  => 'Need ID or Name!'
246        );
247        return;
248    }
249
250    # get cache object
251    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
252
253    # check cache
254    my $CacheKey;
255    if ( $Param{ID} ) {
256        $CacheKey = 'DynamicFieldGet::ID::' . $Param{ID};
257    }
258    else {
259        $CacheKey = 'DynamicFieldGet::Name::' . $Param{Name};
260
261    }
262    my $Cache = $CacheObject->Get(
263        Type => 'DynamicField',
264        Key  => $CacheKey,
265    );
266    return $Cache if $Cache;
267
268    # get database object
269    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
270
271    # sql
272    if ( $Param{ID} ) {
273        return if !$DBObject->Prepare(
274            SQL =>
275                'SELECT id, internal_field, name, label, field_order, field_type, object_type, config,'
276                .
277                ' valid_id, create_time, change_time ' .
278                'FROM dynamic_field WHERE id = ?',
279            Bind => [ \$Param{ID} ],
280        );
281    }
282    else {
283        return if !$DBObject->Prepare(
284            SQL =>
285                'SELECT id, internal_field, name, label, field_order, field_type, object_type, config,'
286                .
287                ' valid_id, create_time, change_time ' .
288                'FROM dynamic_field WHERE name = ?',
289            Bind => [ \$Param{Name} ],
290        );
291    }
292
293    # get yaml object
294    my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML');
295
296    my %Data;
297    while ( my @Data = $DBObject->FetchrowArray() ) {
298
299        my $Config = $YAMLObject->Load( Data => $Data[7] );
300
301        %Data = (
302            ID            => $Data[0],
303            InternalField => $Data[1],
304            Name          => $Data[2],
305            Label         => $Data[3],
306            FieldOrder    => $Data[4],
307            FieldType     => $Data[5],
308            ObjectType    => $Data[6],
309            Config        => $Config,
310            ValidID       => $Data[8],
311            CreateTime    => $Data[9],
312            ChangeTime    => $Data[10],
313        );
314    }
315
316    if (%Data) {
317
318        # Set the cache only, if the YAML->Load was successful (see bug#12483).
319        if ( $Data{Config} ) {
320
321            $CacheObject->Set(
322                Type  => 'DynamicField',
323                Key   => $CacheKey,
324                Value => \%Data,
325                TTL   => $Self->{CacheTTL},
326            );
327        }
328
329        $Data{Config} ||= {};
330    }
331
332    return \%Data;
333}
334
335=head2 DynamicFieldUpdate()
336
337update Dynamic Field content into database
338
339returns 1 on success or undef on error
340
341    my $Success = $DynamicFieldObject->DynamicFieldUpdate(
342        ID          => 1234,            # mandatory
343        Name        => 'NameForField',  # mandatory
344        Label       => 'a description', # mandatory, label to show
345        FieldOrder  => 123,             # mandatory, display order
346        FieldType   => 'Text',          # mandatory, selects the DF backend to use for this field
347        ObjectType  => 'Article',       # this controls which object the dynamic field links to
348                                        # allow only lowercase letters
349        Config      => $ConfigHashRef,  # it is stored on YAML format
350                                        # to individual articles, otherwise to tickets
351        ValidID     => 1,
352        Reorder     => 1,               # or 0, to trigger reorder function, default 1
353        UserID      => 123,
354    );
355
356=cut
357
358sub DynamicFieldUpdate {
359    my ( $Self, %Param ) = @_;
360
361    # check needed stuff
362    for my $Key (qw(ID Name Label FieldOrder FieldType ObjectType Config ValidID UserID)) {
363        if ( !$Param{$Key} ) {
364            $Kernel::OM->Get('Kernel::System::Log')->Log(
365                Priority => 'error',
366                Message  => "Need $Key!"
367            );
368            return;
369        }
370    }
371
372    my $Reorder;
373    if ( !exists $Param{Reorder} || $Param{Reorder} eq 1 ) {
374        $Reorder = 1;
375    }
376
377    my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML');
378
379    # dump config as string
380    my $Config = $YAMLObject->Dump(
381        Data => $Param{Config},
382    );
383
384    # Make sure the resulting string has the UTF-8 flag. YAML only sets it if
385    #    part of the data already had it.
386    utf8::upgrade($Config);
387
388    return if !$YAMLObject->Load( Data => $Config );
389
390    # check needed structure for some fields
391    if ( $Param{Name} !~ m{ \A [a-zA-Z\d]+ \z }xms ) {
392        $Kernel::OM->Get('Kernel::System::Log')->Log(
393            Priority => 'error',
394            Message  => "Not valid letters on Name:$Param{Name} or ObjectType:$Param{ObjectType}!",
395        );
396        return;
397    }
398
399    # get database object
400    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
401
402    # check if Name already exists
403    return if !$DBObject->Prepare(
404        SQL => "SELECT id FROM dynamic_field "
405            . "WHERE $Self->{Lower}(name) = $Self->{Lower}(?) "
406            . "AND id != ?",
407        Bind  => [ \$Param{Name}, \$Param{ID} ],
408        LIMIT => 1,
409    );
410
411    my $NameExists;
412    while ( my @Data = $DBObject->FetchrowArray() ) {
413        $NameExists = 1;
414    }
415
416    if ($NameExists) {
417        $Kernel::OM->Get('Kernel::System::Log')->Log(
418            Priority => 'error',
419            Message  => "A dynamic field with the name '$Param{Name}' already exists.",
420        );
421        return;
422    }
423
424    if ( $Param{FieldOrder} !~ m{ \A [\d]+ \z }xms ) {
425        $Kernel::OM->Get('Kernel::System::Log')->Log(
426            Priority => 'error',
427            Message  => "Not valid number on FieldOrder:$Param{FieldOrder}!",
428        );
429        return;
430    }
431
432    # get the old dynamic field data
433    my $OldDynamicField = $Self->DynamicFieldGet(
434        ID => $Param{ID},
435    );
436
437    # check if FieldOrder is changed
438    my $ChangedOrder;
439    if ( $OldDynamicField->{FieldOrder} ne $Param{FieldOrder} ) {
440        $ChangedOrder = 1;
441    }
442
443    # sql
444    return if !$DBObject->Do(
445        SQL => 'UPDATE dynamic_field SET name = ?, label = ?, field_order =?, field_type = ?, '
446            . 'object_type = ?, config = ?, valid_id = ?, change_time = current_timestamp, '
447            . ' change_by = ? WHERE id = ?',
448        Bind => [
449            \$Param{Name}, \$Param{Label}, \$Param{FieldOrder}, \$Param{FieldType},
450            \$Param{ObjectType}, \$Config, \$Param{ValidID}, \$Param{UserID}, \$Param{ID},
451        ],
452    );
453
454    # get cache object
455    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
456
457    # delete cache
458    $CacheObject->CleanUp(
459        Type => 'DynamicField',
460    );
461    $CacheObject->CleanUp(
462        Type => 'DynamicFieldValue',
463    );
464
465    # get the new dynamic field data
466    my $NewDynamicField = $Self->DynamicFieldGet(
467        ID => $Param{ID},
468    );
469
470    # trigger event
471    $Self->EventHandler(
472        Event => 'DynamicFieldUpdate',
473        Data  => {
474            NewData => $NewDynamicField,
475            OldData => $OldDynamicField,
476        },
477        UserID => $Param{UserID},
478    );
479
480    # re-order field list if a change in the order was made
481    if ( $Reorder && $ChangedOrder ) {
482        my $Success = $Self->_DynamicFieldReorder(
483            ID            => $Param{ID},
484            FieldOrder    => $Param{FieldOrder},
485            Mode          => 'Update',
486            OldFieldOrder => $OldDynamicField->{FieldOrder},
487        );
488    }
489
490    return 1;
491}
492
493=head2 DynamicFieldDelete()
494
495delete a Dynamic field entry. You need to make sure that all values are
496deleted before calling this function, otherwise it will fail on DBMS which check
497referential integrity.
498
499returns 1 if successful or undef otherwise
500
501    my $Success = $DynamicFieldObject->DynamicFieldDelete(
502        ID      => 123,
503        UserID  => 123,
504        Reorder => 1,               # or 0, to trigger reorder function, default 1
505    );
506
507=cut
508
509sub DynamicFieldDelete {
510    my ( $Self, %Param ) = @_;
511
512    # check needed stuff
513    for my $Key (qw(ID UserID)) {
514        if ( !$Param{$Key} ) {
515            $Kernel::OM->Get('Kernel::System::Log')->Log(
516                Priority => 'error',
517                Message  => "Need $Key!"
518            );
519            return;
520        }
521    }
522
523    # check if exists
524    my $DynamicField = $Self->DynamicFieldGet(
525        ID => $Param{ID},
526    );
527    return if !IsHashRefWithData($DynamicField);
528
529    # re-order before delete
530    if ( !exists $Param{Reorder} || $Param{Reorder} ) {
531        my $Success = $Self->_DynamicFieldReorder(
532            ID         => $DynamicField->{ID},
533            FieldOrder => $DynamicField->{FieldOrder},
534            Mode       => 'Delete',
535        );
536    }
537
538    # delete dynamic field
539    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
540        SQL  => 'DELETE FROM dynamic_field WHERE id = ?',
541        Bind => [ \$Param{ID} ],
542    );
543
544    # get cache object
545    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
546
547    # delete cache
548    $CacheObject->CleanUp(
549        Type => 'DynamicField',
550    );
551    $CacheObject->CleanUp(
552        Type => 'DynamicFieldValue',
553    );
554
555    # trigger event
556    $Self->EventHandler(
557        Event => 'DynamicFieldDelete',
558        Data  => {
559            NewData => $DynamicField,
560        },
561        UserID => $Param{UserID},
562    );
563
564    return 1;
565}
566
567=head2 DynamicFieldList()
568
569get DynamicField list ordered by the the "Field Order" field in the DB
570
571    my $List = $DynamicFieldObject->DynamicFieldList();
572
573    or
574
575    my $List = $DynamicFieldObject->DynamicFieldList(
576        Valid => 0,             # optional, defaults to 1
577
578        # object  type (optional) as STRING or as ARRAYREF
579        ObjectType => 'Ticket',
580        ObjectType => ['Ticket', 'Article'],
581
582        ResultType => 'HASH',   # optional, 'ARRAY' or 'HASH', defaults to 'ARRAY'
583
584        FieldFilter => {        # optional, only active fields (non 0) will be returned
585            ItemOne   => 1,
586            ItemTwo   => 2,
587            ItemThree => 1,
588            ItemFour  => 1,
589            ItemFive  => 0,
590        },
591
592    );
593
594Returns:
595
596    $List = {
597        1 => 'ItemOne',
598        2 => 'ItemTwo',
599        3 => 'ItemThree',
600        4 => 'ItemFour',
601    };
602
603    or
604
605    $List = (
606        1,
607        2,
608        3,
609        4
610    );
611
612=cut
613
614sub DynamicFieldList {
615    my ( $Self, %Param ) = @_;
616
617    # to store fieldIDs white-list
618    my %AllowedFieldIDs;
619
620    if ( defined $Param{FieldFilter} && ref $Param{FieldFilter} eq 'HASH' ) {
621
622        # fill the fieldIDs white-list
623        FIELDNAME:
624        for my $FieldName ( sort keys %{ $Param{FieldFilter} } ) {
625            next FIELDNAME if !$Param{FieldFilter}->{$FieldName};
626
627            my $FieldConfig = $Self->DynamicFieldGet( Name => $FieldName );
628            next FIELDNAME if !IsHashRefWithData($FieldConfig);
629            next FIELDNAME if !$FieldConfig->{ID};
630
631            $AllowedFieldIDs{ $FieldConfig->{ID} } = 1;
632        }
633    }
634
635    # check cache
636    my $Valid = 1;
637    if ( defined $Param{Valid} && $Param{Valid} eq '0' ) {
638        $Valid = 0;
639    }
640
641    # set cache key object type component depending on the ObjectType parameter
642    my $ObjectType = 'All';
643    if ( IsArrayRefWithData( $Param{ObjectType} ) ) {
644        $ObjectType = join '_', sort @{ $Param{ObjectType} };
645    }
646    elsif ( IsStringWithData( $Param{ObjectType} ) ) {
647        $ObjectType = $Param{ObjectType};
648    }
649
650    my $ResultType = $Param{ResultType} || 'ARRAY';
651    $ResultType = $ResultType eq 'HASH' ? 'HASH' : 'ARRAY';
652
653    # get cache object
654    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
655
656    my $CacheKey = 'DynamicFieldList::Valid::'
657        . $Valid
658        . '::ObjectType::'
659        . $ObjectType
660        . '::ResultType::'
661        . $ResultType;
662    my $Cache = $CacheObject->Get(
663        Type => 'DynamicField',
664        Key  => $CacheKey,
665    );
666
667    if ($Cache) {
668
669        # check if FieldFilter is not set
670        if ( !defined $Param{FieldFilter} ) {
671
672            # return raw data from cache
673            return $Cache;
674        }
675        elsif ( ref $Param{FieldFilter} ne 'HASH' ) {
676            $Kernel::OM->Get('Kernel::System::Log')->Log(
677                Priority => 'error',
678                Message  => 'FieldFilter must be a HASH reference!',
679            );
680            return;
681        }
682
683        # otherwise apply the filter
684        my $FilteredData;
685
686        # check if cache is ARRAY ref
687        if ( $ResultType eq 'ARRAY' ) {
688
689            FIELDID:
690            for my $FieldID ( @{$Cache} ) {
691                next FIELDID if !$AllowedFieldIDs{$FieldID};
692
693                push @{$FilteredData}, $FieldID;
694            }
695
696            # return filtered data from cache
697            return $FilteredData;
698        }
699
700        # otherwise is a HASH ref
701        else {
702
703            FIELDID:
704            for my $FieldID ( sort keys %{$Cache} ) {
705                next FIELDID if !$AllowedFieldIDs{$FieldID};
706
707                $FilteredData->{$FieldID} = $Cache->{$FieldID};
708            }
709        }
710
711        # return filtered data from cache
712        return $FilteredData;
713    }
714
715    else {
716        my $SQL = 'SELECT id, name, field_order FROM dynamic_field';
717
718        # get database object
719        my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
720
721        if ($Valid) {
722
723            # get valid object
724            my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid');
725
726            $SQL .= ' WHERE valid_id IN (' . join ', ', $ValidObject->ValidIDsGet() . ')';
727
728            if ( $Param{ObjectType} ) {
729                if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) {
730                    $SQL .=
731                        " AND object_type = '"
732                        . $DBObject->Quote( $Param{ObjectType} ) . "'";
733                }
734                elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) {
735                    my $ObjectTypeString =
736                        join ',',
737                        map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} };
738                    $SQL .= " AND object_type IN ($ObjectTypeString)";
739
740                }
741            }
742        }
743        else {
744            if ( $Param{ObjectType} ) {
745                if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) {
746                    $SQL .=
747                        " WHERE object_type = '"
748                        . $DBObject->Quote( $Param{ObjectType} ) . "'";
749                }
750                elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) {
751                    my $ObjectTypeString =
752                        join ',',
753                        map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} };
754                    $SQL .= " WHERE object_type IN ($ObjectTypeString)";
755                }
756            }
757        }
758
759        $SQL .= " ORDER BY field_order, id";
760
761        return if !$DBObject->Prepare( SQL => $SQL );
762
763        if ( $ResultType eq 'HASH' ) {
764            my %Data;
765
766            while ( my @Row = $DBObject->FetchrowArray() ) {
767                $Data{ $Row[0] } = $Row[1];
768            }
769
770            # set cache
771            $CacheObject->Set(
772                Type  => 'DynamicField',
773                Key   => $CacheKey,
774                Value => \%Data,
775                TTL   => $Self->{CacheTTL},
776            );
777
778            # check if FieldFilter is not set
779            if ( !defined $Param{FieldFilter} ) {
780
781                # return raw data from DB
782                return \%Data;
783            }
784            elsif ( ref $Param{FieldFilter} ne 'HASH' ) {
785                $Kernel::OM->Get('Kernel::System::Log')->Log(
786                    Priority => 'error',
787                    Message  => 'FieldFilter must be a HASH reference!',
788                );
789                return;
790            }
791
792            my %FilteredData;
793            FIELDID:
794            for my $FieldID ( sort keys %Data ) {
795                next FIELDID if !$AllowedFieldIDs{$FieldID};
796
797                $FilteredData{$FieldID} = $Data{$FieldID};
798            }
799
800            # return filtered data from DB
801            return \%FilteredData;
802        }
803
804        else {
805
806            my @Data;
807            while ( my @Row = $DBObject->FetchrowArray() ) {
808                push @Data, $Row[0];
809            }
810
811            # set cache
812            $CacheObject->Set(
813                Type  => 'DynamicField',
814                Key   => $CacheKey,
815                Value => \@Data,
816                TTL   => $Self->{CacheTTL},
817            );
818
819            # check if FieldFilter is not set
820            if ( !defined $Param{FieldFilter} ) {
821
822                # return raw data from DB
823                return \@Data;
824            }
825            elsif ( ref $Param{FieldFilter} ne 'HASH' ) {
826                $Kernel::OM->Get('Kernel::System::Log')->Log(
827                    Priority => 'error',
828                    Message  => 'FieldFilter must be a HASH reference!',
829                );
830                return;
831            }
832
833            my @FilteredData;
834            FIELDID:
835            for my $FieldID (@Data) {
836                next FIELDID if !$AllowedFieldIDs{$FieldID};
837
838                push @FilteredData, $FieldID;
839            }
840
841            # return filtered data from DB
842            return \@FilteredData;
843        }
844    }
845
846    return;
847}
848
849=head2 DynamicFieldListGet()
850
851get DynamicField list with complete data ordered by the "Field Order" field in the DB
852
853    my $List = $DynamicFieldObject->DynamicFieldListGet();
854
855    or
856
857    my $List = $DynamicFieldObject->DynamicFieldListGet(
858        Valid        => 0,            # optional, defaults to 1
859
860        # object  type (optional) as STRING or as ARRAYREF
861        ObjectType => 'Ticket',
862        ObjectType => ['Ticket', 'Article'],
863
864        FieldFilter => {        # optional, only active fields (non 0) will be returned
865            nameforfield => 1,
866            fieldname    => 2,
867            other        => 0,
868            otherfield   => 0,
869        },
870
871    );
872
873Returns:
874
875    $List = (
876        {
877            ID          => 123,
878            InternalField => 0,
879            Name        => 'nameforfield',
880            Label       => 'The label to show',
881            FieldType   => 'Text',
882            ObjectType  => 'Article',
883            Config      => $ConfigHashRef,
884            ValidID     => 1,
885            CreateTime  => '2011-02-08 15:08:00',
886            ChangeTime  => '2011-06-11 17:22:00',
887        },
888        {
889            ID            => 321,
890            InternalField => 0,
891            Name          => 'fieldname',
892            Label         => 'It is not a label',
893            FieldType     => 'Text',
894            ObjectType    => 'Ticket',
895            Config        => $ConfigHashRef,
896            ValidID       => 1,
897            CreateTime    => '2010-09-11 10:08:00',
898            ChangeTime    => '2011-01-01 01:01:01',
899        },
900        ...
901    );
902
903=cut
904
905sub DynamicFieldListGet {
906    my ( $Self, %Param ) = @_;
907
908    # check cache
909    my $Valid = 1;
910    if ( defined $Param{Valid} && $Param{Valid} eq '0' ) {
911        $Valid = 0;
912    }
913
914    # set cache key object type component depending on the ObjectType parameter
915    my $ObjectType = 'All';
916    if ( IsArrayRefWithData( $Param{ObjectType} ) ) {
917        $ObjectType = join '_', sort @{ $Param{ObjectType} };
918    }
919    elsif ( IsStringWithData( $Param{ObjectType} ) ) {
920        $ObjectType = $Param{ObjectType};
921    }
922
923    # get cache object
924    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
925
926    my $CacheKey = 'DynamicFieldListGet::Valid::' . $Valid . '::ObjectType::' . $ObjectType;
927    my $Cache    = $CacheObject->Get(
928        Type => 'DynamicField',
929        Key  => $CacheKey,
930    );
931
932    if ($Cache) {
933
934        # check if FieldFilter is not set
935        if ( !defined $Param{FieldFilter} ) {
936
937            # return raw data from cache
938            return $Cache;
939        }
940        elsif ( ref $Param{FieldFilter} ne 'HASH' ) {
941            $Kernel::OM->Get('Kernel::System::Log')->Log(
942                Priority => 'error',
943                Message  => 'FieldFilter must be a HASH reference!',
944            );
945            return;
946        }
947
948        my $FilteredData;
949
950        DYNAMICFIELD:
951        for my $DynamicFieldConfig ( @{$Cache} ) {
952            next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
953            next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
954            next DYNAMICFIELD if !$Param{FieldFilter}->{ $DynamicFieldConfig->{Name} };
955
956            push @{$FilteredData}, $DynamicFieldConfig;
957        }
958
959        # return filtered data from cache
960        return $FilteredData;
961    }
962
963    # get database object
964    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
965
966    my @Data;
967    my $SQL = 'SELECT id, name, field_order FROM dynamic_field';
968
969    if ($Valid) {
970
971        # get valid object
972        my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid');
973
974        $SQL .= ' WHERE valid_id IN (' . join ', ', $ValidObject->ValidIDsGet() . ')';
975
976        if ( $Param{ObjectType} ) {
977            if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) {
978                $SQL .=
979                    " AND object_type = '" . $DBObject->Quote( $Param{ObjectType} ) . "'";
980            }
981            elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) {
982                my $ObjectTypeString =
983                    join ',',
984                    map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} };
985                $SQL .= " AND object_type IN ($ObjectTypeString)";
986
987            }
988        }
989    }
990    else {
991        if ( $Param{ObjectType} ) {
992            if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) {
993                $SQL .=
994                    " WHERE object_type = '" . $DBObject->Quote( $Param{ObjectType} ) . "'";
995            }
996            elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) {
997                my $ObjectTypeString =
998                    join ',',
999                    map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} };
1000                $SQL .= " WHERE object_type IN ($ObjectTypeString)";
1001            }
1002        }
1003    }
1004
1005    $SQL .= " ORDER BY field_order, id";
1006
1007    return if !$DBObject->Prepare( SQL => $SQL );
1008
1009    my @DynamicFieldIDs;
1010    while ( my @Row = $DBObject->FetchrowArray() ) {
1011        push @DynamicFieldIDs, $Row[0];
1012    }
1013
1014    for my $ItemID (@DynamicFieldIDs) {
1015
1016        my $DynamicField = $Self->DynamicFieldGet(
1017            ID => $ItemID,
1018        );
1019        push @Data, $DynamicField;
1020    }
1021
1022    # set cache
1023    $CacheObject->Set(
1024        Type  => 'DynamicField',
1025        Key   => $CacheKey,
1026        Value => \@Data,
1027        TTL   => $Self->{CacheTTL},
1028    );
1029
1030    # check if FieldFilter is not set
1031    if ( !defined $Param{FieldFilter} ) {
1032
1033        # return raw data from DB
1034        return \@Data;
1035    }
1036    elsif ( ref $Param{FieldFilter} ne 'HASH' ) {
1037        $Kernel::OM->Get('Kernel::System::Log')->Log(
1038            Priority => 'error',
1039            Message  => 'FieldFilter must be a HASH reference!',
1040        );
1041        return;
1042    }
1043
1044    my $FilteredData;
1045
1046    DYNAMICFIELD:
1047    for my $DynamicFieldConfig (@Data) {
1048        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
1049        next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
1050        next DYNAMICFIELD if !$Param{FieldFilter}->{ $DynamicFieldConfig->{Name} };
1051
1052        push @{$FilteredData}, $DynamicFieldConfig;
1053    }
1054
1055    # return filtered data from DB
1056    return $FilteredData;
1057}
1058
1059=head2 DynamicFieldOrderReset()
1060
1061sets the order of all dynamic fields based on a consecutive number list starting with number 1.
1062This function will remove duplicate order numbers and gaps in the numbering.
1063
1064    my $Success = $DynamicFieldObject->DynamicFieldOrderReset();
1065
1066Returns:
1067
1068    $Success = 1;                        # or 0 in case of error
1069
1070=cut
1071
1072sub DynamicFieldOrderReset {
1073    my ( $Self, %Param ) = @_;
1074
1075    # get all fields
1076    my $DynamicFieldList = $Self->DynamicFieldListGet(
1077        Valid => 0,
1078    );
1079
1080    # to set the field order
1081    my $Counter;
1082
1083    # loop through all the dynamic fields
1084    DYNAMICFIELD:
1085    for my $DynamicField ( @{$DynamicFieldList} ) {
1086
1087        # prepare the new field order
1088        $Counter++;
1089
1090        # skip wrong fields (if any)
1091        next DYNAMICFIELD if !IsHashRefWithData($DynamicField);
1092
1093        # skip fields with the correct order
1094        next DYNAMICFIELD if $DynamicField->{FieldOrder} eq $Counter;
1095
1096        $DynamicField->{FieldOrder} = $Counter;
1097
1098        # update the database
1099        my $Success = $Self->DynamicFieldUpdate(
1100            %{$DynamicField},
1101            UserID  => 1,
1102            Reorder => 0,
1103        );
1104
1105        # check if the update was successful
1106        if ( !$Success ) {
1107            $Kernel::OM->Get('Kernel::System::Log')->Log(
1108                Priority => 'error',
1109                Message  => 'An error was detected while re ordering the field list on field '
1110                    . "DynamicField->{Name}!",
1111            );
1112            return;
1113        }
1114    }
1115
1116    return 1;
1117}
1118
1119=head2 DynamicFieldOrderCheck()
1120
1121checks for duplicate order numbers and gaps in the numbering.
1122
1123    my $Success = $DynamicFieldObject->DynamicFieldOrderCheck();
1124
1125Returns:
1126
1127    $Success = 1;                       # or 0 in case duplicates or gaps in the dynamic fields
1128                                        #    order numbering
1129
1130=cut
1131
1132sub DynamicFieldOrderCheck {
1133    my ( $Self, %Param ) = @_;
1134
1135    # get all fields
1136    my $DynamicFieldList = $Self->DynamicFieldListGet(
1137        Valid => 0,
1138    );
1139
1140    # to had a correct order reference
1141    my $Counter;
1142
1143    # flag to be activated if the order is not correct
1144    my $OrderError;
1145
1146    # loop through all the dynamic fields
1147    DYNAMICFIELD:
1148    for my $DynamicField ( @{$DynamicFieldList} ) {
1149        $Counter++;
1150
1151        # skip wrong fields (if any)
1152        next DYNAMICFIELD if !IsHashRefWithData($DynamicField);
1153
1154        # skip fields with correct order
1155        next DYNAMICFIELD if $DynamicField->{FieldOrder} eq $Counter;
1156
1157        # when finding a field with wrong order, set OrderError flag and exit loop
1158        $OrderError = 1;
1159        last DYNAMICFIELD;
1160    }
1161
1162    return if $OrderError;
1163
1164    return 1;
1165}
1166
1167=head2 ObjectMappingGet()
1168
1169(a) Fetches object ID(s) for given object name(s).
1170(b) Fetches object name(s) for given object ID(s).
1171
1172NOTE: Only use object mappings for dynamic fields that must support non-integer object IDs,
1173like customer user logins and customer company IDs.
1174
1175    my $ObjectMapping = $DynamicFieldObject->ObjectMappingGet(
1176        ObjectName            => $ObjectName,    # Name or array ref of names of the object(s) to get the ID(s) for
1177                                                 # Note: either give ObjectName or ObjectID
1178        ObjectID              => $ObjectID,      # ID or array ref of IDs of the object(s) to get the name(s) for
1179                                                 # Note: either give ObjectName or ObjectID
1180        ObjectType            => 'CustomerUser', # Type of object to get mapping for
1181    );
1182
1183    Returns for parameter ObjectID:
1184    $ObjectMapping = {
1185        ObjectID => ObjectName,
1186        ObjectID => ObjectName,
1187        ObjectID => ObjectName,
1188        # ...
1189    };
1190
1191    Returns for parameter ObjectName:
1192    $ObjectMapping = {
1193        ObjectName => ObjectID,
1194        ObjectName => ObjectID,
1195        ObjectName => ObjectID,
1196        # ...
1197    };
1198
1199=cut
1200
1201sub ObjectMappingGet {
1202    my ( $Self, %Param ) = @_;
1203
1204    # check needed stuff
1205    for my $Needed (qw( ObjectType )) {
1206        if ( !$Param{$Needed} ) {
1207            $Kernel::OM->Get('Kernel::System::Log')->Log(
1208                Priority => 'error',
1209                Message  => "Need $Needed!"
1210            );
1211            return;
1212        }
1213    }
1214
1215    if ( $Param{ObjectName} && $Param{ObjectID} ) {
1216        $Kernel::OM->Get('Kernel::System::Log')->Log(
1217            Priority => 'error',
1218            Message  => "Either give parameter ObjectName or ObjectID, not both."
1219        );
1220        return;
1221    }
1222
1223    if ( !$Param{ObjectName} && !$Param{ObjectID} ) {
1224        $Kernel::OM->Get('Kernel::System::Log')->Log(
1225            Priority => 'error',
1226            Message  => "You have to give parameter ObjectName or ObjectID."
1227        );
1228        return;
1229    }
1230
1231    # Get config object
1232    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1233
1234    # Get configuration for this object type
1235    my $Config           = $ConfigObject->Get("DynamicFields::ObjectType") || {};
1236    my $ObjecTypesConfig = $Config->{ $Param{ObjectType} };
1237
1238    if ( !IsHashRefWithData($ObjecTypesConfig) ) {
1239        $Kernel::OM->Get('Kernel::System::Log')->Log(
1240            Priority => 'error',
1241            Message  => "Configuration for dynamic field object type $Param{ObjectType} is invalid!",
1242        );
1243        return;
1244    }
1245
1246    if ( !$ObjecTypesConfig->{UseObjectName} ) {
1247        $Kernel::OM->Get('Kernel::System::Log')->Log(
1248            Priority => 'error',
1249            Message  => "Dynamic field object type $Param{ObjectType} does not support this function",
1250        );
1251        return;
1252    }
1253
1254    my $Type = $Param{ObjectName} ? 'ObjectName' : 'ObjectID';
1255    if ( !IsArrayRefWithData( $Param{$Type} ) ) {
1256        $Param{$Type} = [
1257            $Param{$Type},
1258        ];
1259    }
1260    my %LookupValues = map { $_ => '?' } @{ $Param{$Type} };
1261
1262    my $CacheKey = 'ObjectMappingGet::'
1263        . $Type . '::'
1264        . ( join ',', sort keys %LookupValues ) . '::'
1265        . $Param{ObjectType};
1266    my $CacheType = 'DynamicFieldObjectMapping' . $Type;
1267
1268    # Get cache object.
1269    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
1270    my $Cache       = $CacheObject->Get(
1271        Type => $CacheType,
1272        Key  => $CacheKey,
1273    );
1274
1275    return $Cache if IsHashRefWithData($Cache);
1276
1277    my $SQL;
1278    if ( $Type eq 'ObjectID' ) {
1279        $SQL = '
1280            SELECT object_id, object_name
1281            FROM  dynamic_field_obj_id_name
1282            WHERE object_id IN (' . ( join ', ', values %LookupValues ) . ')
1283                AND object_type = ?';
1284    }
1285    else {
1286        $SQL = '
1287            SELECT object_name, object_id
1288            FROM dynamic_field_obj_id_name
1289            WHERE object_name IN (' . ( join ', ', values %LookupValues ) . ')
1290                AND object_type = ?';
1291    }
1292
1293    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
1294    return if !$DBObject->Prepare(
1295        SQL  => $SQL,
1296        Bind => [
1297            \keys %LookupValues,
1298            \$Param{ObjectType},
1299        ],
1300    );
1301
1302    my %ObjectMapping;
1303    while ( my @Data = $DBObject->FetchrowArray() ) {
1304        $ObjectMapping{ $Data[0] } = $Data[1];
1305    }
1306
1307    # set cache
1308    my $CacheTTL = $ConfigObject->Get('DynamicField::CacheTTL') || 60 * 60 * 12;
1309    $CacheObject->Set(
1310        Type  => $CacheType,
1311        Key   => $CacheKey,
1312        Value => \%ObjectMapping,
1313        TTL   => $CacheTTL,
1314    );
1315
1316    return \%ObjectMapping;
1317}
1318
1319=head2 ObjectMappingCreate()
1320
1321Creates an object mapping for the given given object name.
1322
1323NOTE: Only use object mappings for dynamic fields that must support non-integer object IDs,
1324like customer user logins and customer company IDs.
1325
1326    my $ObjectID = $DynamicFieldObject->ObjectMappingCreate(
1327        ObjectName => 'customer-1',   # Name of the object to create the mapping for
1328        ObjectType => 'CustomerUser', # Type of object to create the mapping for
1329    );
1330
1331=cut
1332
1333sub ObjectMappingCreate {
1334    my ( $Self, %Param ) = @_;
1335
1336    # check needed stuff
1337    for my $Needed (qw( ObjectName ObjectType )) {
1338        if ( !defined $Param{$Needed} || !length $Param{$Needed} ) {
1339            $Kernel::OM->Get('Kernel::System::Log')->Log(
1340                Priority => 'error',
1341                Message  => "Need $Needed!"
1342            );
1343            return;
1344        }
1345    }
1346
1347    # Get configuration for this object type
1348    my $Config           = $Kernel::OM->Get('Kernel::Config')->Get("DynamicFields::ObjectType") || {};
1349    my $ObjecTypesConfig = $Config->{ $Param{ObjectType} };
1350
1351    if ( !IsHashRefWithData($ObjecTypesConfig) ) {
1352        $Kernel::OM->Get('Kernel::System::Log')->Log(
1353            Priority => 'error',
1354            Message  => "Configuration for dynamic field object type $Param{ObjectType} is invalid!",
1355        );
1356        return;
1357    }
1358
1359    if ( !$ObjecTypesConfig->{UseObjectName} ) {
1360        $Kernel::OM->Get('Kernel::System::Log')->Log(
1361            Priority => 'error',
1362            Message  => "Dynamic field object type $Param{ObjectType} does not support this function",
1363        );
1364        return;
1365    }
1366
1367    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
1368    return if !$DBObject->Do(
1369        SQL => '
1370            INSERT INTO dynamic_field_obj_id_name
1371                (object_name, object_type)
1372            VALUES
1373                (?, ?)',
1374        Bind => [
1375            \$Param{ObjectName},
1376            \$Param{ObjectType},
1377        ],
1378    );
1379
1380    return if !$DBObject->Prepare(
1381        SQL => '
1382            SELECT object_id
1383            FROM dynamic_field_obj_id_name
1384            WHERE object_name = ?
1385                AND object_type = ?',
1386        Bind => [
1387            \$Param{ObjectName},
1388            \$Param{ObjectType},
1389        ],
1390        Limit => 1,
1391    );
1392
1393    my $ObjectID;
1394    while ( my @Data = $DBObject->FetchrowArray() ) {
1395        $ObjectID = $Data[0];
1396    }
1397
1398    return $ObjectID;
1399}
1400
1401=head2 ObjectMappingNameChange()
1402
1403Changes name of given object mapping.
1404
1405NOTE: Only use object mappings for dynamic fields that must support non-integer object IDs,
1406like customer user logins and customer company IDs.
1407
1408
1409    my $Success = $DynamicFieldObject->ObjectMappingNameChange(
1410        OldObjectName => 'customer-1',
1411        NewObjectName => 'customer-2',
1412        ObjectType    => 'CustomerUser', # Type of object to change name for
1413    );
1414
1415    Returns 1 on success.
1416
1417=cut
1418
1419sub ObjectMappingNameChange {
1420    my ( $Self, %Param ) = @_;
1421
1422    # check needed stuff
1423    for my $Needed (qw( OldObjectName NewObjectName ObjectType )) {
1424        if ( !defined $Param{$Needed} || !length $Param{$Needed} ) {
1425            $Kernel::OM->Get('Kernel::System::Log')->Log(
1426                Priority => 'error',
1427                Message  => "Need $Needed!"
1428            );
1429            return;
1430        }
1431    }
1432
1433    # Get configuration for this object type
1434    my $Config           = $Kernel::OM->Get('Kernel::Config')->Get("DynamicFields::ObjectType") || {};
1435    my $ObjecTypesConfig = $Config->{ $Param{ObjectType} };
1436
1437    if ( !IsHashRefWithData($ObjecTypesConfig) ) {
1438        $Kernel::OM->Get('Kernel::System::Log')->Log(
1439            Priority => 'error',
1440            Message  => "Configuration for dynamic field object type $Param{ObjectType} is invalid!",
1441        );
1442        return;
1443    }
1444
1445    if ( !$ObjecTypesConfig->{UseObjectName} ) {
1446        $Kernel::OM->Get('Kernel::System::Log')->Log(
1447            Priority => 'error',
1448            Message  => "Dynamic field object type $Param{ObjectType} does not support this function",
1449        );
1450        return;
1451    }
1452
1453    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
1454    return if !$DBObject->Do(
1455        SQL => '
1456            UPDATE dynamic_field_obj_id_name
1457            SET object_name = ?
1458            WHERE object_name = ?
1459                AND object_type = ?',
1460        Bind => [
1461            \$Param{NewObjectName},
1462            \$Param{OldObjectName},
1463            \$Param{ObjectType},
1464        ],
1465    );
1466
1467    # Clean up cache for type DynamicFieldValueObjectName.
1468    # A cleanup based on the changed object name is not possible.
1469    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
1470    $CacheObject->CleanUp(
1471        Type => 'DynamicFieldObjectMappingObjectID',
1472    );
1473    $CacheObject->CleanUp(
1474        Type => 'DynamicFieldObjectMappingObjectName',
1475    );
1476
1477    return 1;
1478}
1479
1480sub DESTROY {
1481    my $Self = shift;
1482
1483    # execute all transaction events
1484    $Self->EventHandlerTransaction();
1485
1486    return 1;
1487}
1488
1489=begin Internal:
1490
1491=cut
1492
1493=head2 _DynamicFieldReorder()
1494
1495re-order the list of fields.
1496
1497    $Success = $DynamicFieldObject->_DynamicFieldReorder(
1498        ID         => 123,              # mandatory, the field ID that triggers the re-order
1499        Mode       => 'Add',            # || Update || Delete
1500        FieldOrder => 2,                # mandatory, the FieldOrder from the trigger field
1501    );
1502
1503    $Success = $DynamicFieldObject->_DynamicFieldReorder(
1504        ID            => 123,           # mandatory, the field ID that triggers the re-order
1505        Mode          => 'Update',      # || Update || Delete
1506        FieldOrder    => 2,             # mandatory, the FieldOrder from the trigger field
1507        OldFieldOrder => 10,            # mandatory for Mode = 'Update', the FieldOrder before the
1508                                        # update
1509    );
1510
1511=cut
1512
1513sub _DynamicFieldReorder {
1514    my ( $Self, %Param ) = @_;
1515
1516    # check needed stuff
1517    for my $Needed (qw(ID FieldOrder Mode)) {
1518        if ( !$Param{$Needed} ) {
1519            $Kernel::OM->Get('Kernel::System::Log')->Log(
1520                Priority => 'error',
1521                Message  => 'Need $Needed!'
1522            );
1523            return;
1524        }
1525    }
1526
1527    if ( $Param{Mode} eq 'Update' ) {
1528
1529        # check needed stuff
1530        if ( !$Param{OldFieldOrder} ) {
1531            $Kernel::OM->Get('Kernel::System::Log')->Log(
1532                Priority => 'error',
1533                Message  => 'Need OldFieldOrder!'
1534            );
1535            return;
1536        }
1537    }
1538
1539    # get the Dynamic Field trigger
1540    my $DynamicFieldTrigger = $Self->DynamicFieldGet(
1541        ID => $Param{ID},
1542    );
1543
1544    # extract the field order from the params
1545    my $TriggerFieldOrder = $Param{FieldOrder};
1546
1547    # get all fields
1548    my $DynamicFieldList = $Self->DynamicFieldListGet(
1549        Valid => 0,
1550    );
1551
1552    # to store the fields that need to be updated
1553    my @NeedToUpdateList;
1554
1555    # to add or subtract the field order by 1
1556    my $Substract;
1557
1558    # update and add has different algorithms to select the fields to be updated
1559    # check if update
1560    if ( $Param{Mode} eq 'Update' ) {
1561        my $OldFieldOrder = $Param{OldFieldOrder};
1562
1563        # if the new order and the old order are equal no operation should be performed
1564        # this is a double check from DynamicFieldUpdate (is case of the function is called
1565        # from outside)
1566        return if $TriggerFieldOrder eq $OldFieldOrder;
1567
1568        # set subtract mode for selected fields
1569        if ( $TriggerFieldOrder > $OldFieldOrder ) {
1570            $Substract = 1;
1571        }
1572
1573        # identify fields that needs to be updated
1574        DYNAMICFIELD:
1575        for my $DynamicField ( @{$DynamicFieldList} ) {
1576
1577            # skip wrong fields (if any)
1578            next DYNAMICFIELD if !IsHashRefWithData($DynamicField);
1579
1580            my $CurrentOrder = $DynamicField->{FieldOrder};
1581
1582            # skip fields with lower order number
1583            next DYNAMICFIELD
1584                if $CurrentOrder < $OldFieldOrder && $CurrentOrder < $TriggerFieldOrder;
1585
1586            # skip trigger field
1587            next DYNAMICFIELD
1588                if ( $CurrentOrder eq $TriggerFieldOrder && $DynamicField->{ID} eq $Param{ID} );
1589
1590            # skip this and the rest if has greater order number
1591            last DYNAMICFIELD
1592                if $CurrentOrder > $OldFieldOrder && $CurrentOrder > $TriggerFieldOrder;
1593
1594            push @NeedToUpdateList, $DynamicField;
1595        }
1596    }
1597
1598    # check if delete action
1599    elsif ( $Param{Mode} eq 'Delete' ) {
1600
1601        $Substract = 1;
1602
1603        # identify fields that needs to be updated
1604        DYNAMICFIELD:
1605        for my $DynamicField ( @{$DynamicFieldList} ) {
1606
1607            # skip wrong fields (if any)
1608            next DYNAMICFIELD if !IsHashRefWithData($DynamicField);
1609
1610            my $CurrentOrder = $DynamicField->{FieldOrder};
1611
1612            # skip fields with lower order number
1613            next DYNAMICFIELD
1614                if $CurrentOrder < $TriggerFieldOrder;
1615
1616            # skip trigger field
1617            next DYNAMICFIELD
1618                if ( $CurrentOrder eq $TriggerFieldOrder && $DynamicField->{ID} eq $Param{ID} );
1619
1620            push @NeedToUpdateList, $DynamicField;
1621        }
1622    }
1623
1624    # otherwise is add action
1625    else {
1626
1627        # identify fields that needs to be updated
1628        DYNAMICFIELD:
1629        for my $DynamicField ( @{$DynamicFieldList} ) {
1630
1631            # skip wrong fields (if any)
1632            next DYNAMICFIELD if !IsHashRefWithData($DynamicField);
1633
1634            my $CurrentOrder = $DynamicField->{FieldOrder};
1635
1636            # skip fields with lower order number
1637            next DYNAMICFIELD
1638                if $CurrentOrder < $TriggerFieldOrder;
1639
1640            # skip trigger field
1641            next DYNAMICFIELD
1642                if ( $CurrentOrder eq $TriggerFieldOrder && $DynamicField->{ID} eq $Param{ID} );
1643
1644            push @NeedToUpdateList, $DynamicField;
1645        }
1646    }
1647
1648    # update the fields order incrementing or decrementing by 1
1649    for my $DynamicField (@NeedToUpdateList) {
1650
1651        # hash ref validation is not needed since it was validated before
1652        # check if need to add or subtract
1653        if ($Substract) {
1654
1655            # subtract 1 to the dynamic field order value
1656            $DynamicField->{FieldOrder}--;
1657        }
1658        else {
1659
1660            # add 1 to the dynamic field order value
1661            $DynamicField->{FieldOrder}++;
1662        }
1663
1664        # update the database
1665        my $Success = $Self->DynamicFieldUpdate(
1666            %{$DynamicField},
1667            UserID  => 1,
1668            Reorder => 0,
1669        );
1670
1671        # check if the update was successful
1672        if ( !$Success ) {
1673            $Kernel::OM->Get('Kernel::System::Log')->Log(
1674                Priority => 'error',
1675                Message  => 'An error was detected while re ordering the field list on field '
1676                    . "DynamicField->{Name}!",
1677            );
1678            return;
1679        }
1680    }
1681
1682    # delete cache
1683    $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
1684        Type => 'DynamicField',
1685    );
1686
1687    return 1;
1688}
1689
1690=end Internal:
1691
1692=head1 TERMS AND CONDITIONS
1693
1694This software is part of the OTRS project (L<https://otrs.org/>).
1695
1696This software comes with ABSOLUTELY NO WARRANTY. For details, see
1697the enclosed file COPYING for license information (GPL). If you
1698did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
1699
1700=cut
1701
17021;
1703