# -- # Copyright (C) 2001-2019 OTRS AG, https://otrs.com/ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you # did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. # -- package Kernel::System::DynamicField; use strict; use warnings; use parent qw(Kernel::System::EventHandler); use Kernel::System::VariableCheck qw(:all); our @ObjectDependencies = ( 'Kernel::Config', 'Kernel::System::Cache', 'Kernel::System::DB', 'Kernel::System::Log', 'Kernel::System::Valid', 'Kernel::System::YAML', ); =head1 NAME Kernel::System::DynamicField =head1 DESCRIPTION DynamicFields backend =head1 PUBLIC INTERFACE =head2 new() create a DynamicField object. Do not use it directly, instead use: my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); =cut sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {}; bless( $Self, $Type ); # get the cache TTL (in seconds) $Self->{CacheTTL} = $Kernel::OM->Get('Kernel::Config')->Get('DynamicField::CacheTTL') || 3600; # set lower if database is case sensitive $Self->{Lower} = ''; if ( $Kernel::OM->Get('Kernel::System::DB')->GetDatabaseFunction('CaseSensitive') ) { $Self->{Lower} = 'LOWER'; } # init of event handler $Self->EventHandlerInit( Config => 'DynamicField::EventModulePost', ); return $Self; } =head2 DynamicFieldAdd() add new Dynamic Field config returns id of new Dynamic field if successful or undef otherwise my $ID = $DynamicFieldObject->DynamicFieldAdd( InternalField => 0, # optional, 0 or 1, internal fields are protected Name => 'NameForField', # mandatory Label => 'a description', # mandatory, label to show FieldOrder => 123, # mandatory, display order FieldType => 'Text', # mandatory, selects the DF backend to use for this field ObjectType => 'Article', # this controls which object the dynamic field links to # allow only lowercase letters Config => $ConfigHashRef, # it is stored on YAML format # to individual articles, otherwise to tickets Reorder => 1, # or 0, to trigger reorder function, default 1 ValidID => 1, UserID => 123, ); Returns: $ID = 567; =cut sub DynamicFieldAdd { my ( $Self, %Param ) = @_; # check needed stuff for my $Key (qw(Name Label FieldOrder FieldType ObjectType Config ValidID UserID)) { if ( !$Param{$Key} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Key!" ); return; } } # check needed structure for some fields if ( $Param{Name} !~ m{ \A [a-zA-Z\d]+ \z }xms ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Not valid letters on Name:$Param{Name}!" ); return; } # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # check if Name already exists return if !$DBObject->Prepare( SQL => "SELECT id FROM dynamic_field WHERE $Self->{Lower}(name) = $Self->{Lower}(?)", Bind => [ \$Param{Name} ], Limit => 1, ); my $NameExists; while ( my @Data = $DBObject->FetchrowArray() ) { $NameExists = 1; } if ($NameExists) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "A dynamic field with the name '$Param{Name}' already exists.", ); return; } if ( $Param{FieldOrder} !~ m{ \A [\d]+ \z }xms ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Not valid number on FieldOrder:$Param{FieldOrder}!" ); return; } # dump config as string my $Config = $Kernel::OM->Get('Kernel::System::YAML')->Dump( Data => $Param{Config} ); # Make sure the resulting string has the UTF-8 flag. YAML only sets it if # part of the data already had it. utf8::upgrade($Config); my $InternalField = $Param{InternalField} ? 1 : 0; # sql return if !$DBObject->Do( SQL => 'INSERT INTO dynamic_field (internal_field, name, label, field_Order, field_type, object_type,' . ' config, valid_id, create_time, create_by, change_time, change_by)' . ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)', Bind => [ \$InternalField, \$Param{Name}, \$Param{Label}, \$Param{FieldOrder}, \$Param{FieldType}, \$Param{ObjectType}, \$Config, \$Param{ValidID}, \$Param{UserID}, \$Param{UserID}, ], ); # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # delete cache $CacheObject->CleanUp( Type => 'DynamicField', ); $CacheObject->CleanUp( Type => 'DynamicFieldValue', ); my $DynamicField = $Self->DynamicFieldGet( Name => $Param{Name}, ); return if !$DynamicField->{ID}; # trigger event $Self->EventHandler( Event => 'DynamicFieldAdd', Data => { NewData => $DynamicField, }, UserID => $Param{UserID}, ); if ( !exists $Param{Reorder} || $Param{Reorder} ) { # re-order field list $Self->_DynamicFieldReorder( ID => $DynamicField->{ID}, FieldOrder => $DynamicField->{FieldOrder}, Mode => 'Add', ); } return $DynamicField->{ID}; } =head2 DynamicFieldGet() get Dynamic Field attributes my $DynamicField = $DynamicFieldObject->DynamicFieldGet( ID => 123, # ID or Name must be provided Name => 'DynamicField', ); Returns: $DynamicField = { ID => 123, InternalField => 0, Name => 'NameForField', Label => 'The label to show', FieldOrder => 123, FieldType => 'Text', ObjectType => 'Article', Config => $ConfigHashRef, ValidID => 1, CreateTime => '2011-02-08 15:08:00', ChangeTime => '2011-06-11 17:22:00', }; =cut sub DynamicFieldGet { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{ID} && !$Param{Name} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need ID or Name!' ); return; } # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # check cache my $CacheKey; if ( $Param{ID} ) { $CacheKey = 'DynamicFieldGet::ID::' . $Param{ID}; } else { $CacheKey = 'DynamicFieldGet::Name::' . $Param{Name}; } my $Cache = $CacheObject->Get( Type => 'DynamicField', Key => $CacheKey, ); return $Cache if $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # sql if ( $Param{ID} ) { return if !$DBObject->Prepare( SQL => 'SELECT id, internal_field, name, label, field_order, field_type, object_type, config,' . ' valid_id, create_time, change_time ' . 'FROM dynamic_field WHERE id = ?', Bind => [ \$Param{ID} ], ); } else { return if !$DBObject->Prepare( SQL => 'SELECT id, internal_field, name, label, field_order, field_type, object_type, config,' . ' valid_id, create_time, change_time ' . 'FROM dynamic_field WHERE name = ?', Bind => [ \$Param{Name} ], ); } # get yaml object my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML'); my %Data; while ( my @Data = $DBObject->FetchrowArray() ) { my $Config = $YAMLObject->Load( Data => $Data[7] ); %Data = ( ID => $Data[0], InternalField => $Data[1], Name => $Data[2], Label => $Data[3], FieldOrder => $Data[4], FieldType => $Data[5], ObjectType => $Data[6], Config => $Config, ValidID => $Data[8], CreateTime => $Data[9], ChangeTime => $Data[10], ); } if (%Data) { # Set the cache only, if the YAML->Load was successful (see bug#12483). if ( $Data{Config} ) { $CacheObject->Set( Type => 'DynamicField', Key => $CacheKey, Value => \%Data, TTL => $Self->{CacheTTL}, ); } $Data{Config} ||= {}; } return \%Data; } =head2 DynamicFieldUpdate() update Dynamic Field content into database returns 1 on success or undef on error my $Success = $DynamicFieldObject->DynamicFieldUpdate( ID => 1234, # mandatory Name => 'NameForField', # mandatory Label => 'a description', # mandatory, label to show FieldOrder => 123, # mandatory, display order FieldType => 'Text', # mandatory, selects the DF backend to use for this field ObjectType => 'Article', # this controls which object the dynamic field links to # allow only lowercase letters Config => $ConfigHashRef, # it is stored on YAML format # to individual articles, otherwise to tickets ValidID => 1, Reorder => 1, # or 0, to trigger reorder function, default 1 UserID => 123, ); =cut sub DynamicFieldUpdate { my ( $Self, %Param ) = @_; # check needed stuff for my $Key (qw(ID Name Label FieldOrder FieldType ObjectType Config ValidID UserID)) { if ( !$Param{$Key} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Key!" ); return; } } my $Reorder; if ( !exists $Param{Reorder} || $Param{Reorder} eq 1 ) { $Reorder = 1; } my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML'); # dump config as string my $Config = $YAMLObject->Dump( Data => $Param{Config}, ); # Make sure the resulting string has the UTF-8 flag. YAML only sets it if # part of the data already had it. utf8::upgrade($Config); return if !$YAMLObject->Load( Data => $Config ); # check needed structure for some fields if ( $Param{Name} !~ m{ \A [a-zA-Z\d]+ \z }xms ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Not valid letters on Name:$Param{Name} or ObjectType:$Param{ObjectType}!", ); return; } # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # check if Name already exists return if !$DBObject->Prepare( SQL => "SELECT id FROM dynamic_field " . "WHERE $Self->{Lower}(name) = $Self->{Lower}(?) " . "AND id != ?", Bind => [ \$Param{Name}, \$Param{ID} ], LIMIT => 1, ); my $NameExists; while ( my @Data = $DBObject->FetchrowArray() ) { $NameExists = 1; } if ($NameExists) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "A dynamic field with the name '$Param{Name}' already exists.", ); return; } if ( $Param{FieldOrder} !~ m{ \A [\d]+ \z }xms ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Not valid number on FieldOrder:$Param{FieldOrder}!", ); return; } # get the old dynamic field data my $OldDynamicField = $Self->DynamicFieldGet( ID => $Param{ID}, ); # check if FieldOrder is changed my $ChangedOrder; if ( $OldDynamicField->{FieldOrder} ne $Param{FieldOrder} ) { $ChangedOrder = 1; } # sql return if !$DBObject->Do( SQL => 'UPDATE dynamic_field SET name = ?, label = ?, field_order =?, field_type = ?, ' . 'object_type = ?, config = ?, valid_id = ?, change_time = current_timestamp, ' . ' change_by = ? WHERE id = ?', Bind => [ \$Param{Name}, \$Param{Label}, \$Param{FieldOrder}, \$Param{FieldType}, \$Param{ObjectType}, \$Config, \$Param{ValidID}, \$Param{UserID}, \$Param{ID}, ], ); # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # delete cache $CacheObject->CleanUp( Type => 'DynamicField', ); $CacheObject->CleanUp( Type => 'DynamicFieldValue', ); # get the new dynamic field data my $NewDynamicField = $Self->DynamicFieldGet( ID => $Param{ID}, ); # trigger event $Self->EventHandler( Event => 'DynamicFieldUpdate', Data => { NewData => $NewDynamicField, OldData => $OldDynamicField, }, UserID => $Param{UserID}, ); # re-order field list if a change in the order was made if ( $Reorder && $ChangedOrder ) { my $Success = $Self->_DynamicFieldReorder( ID => $Param{ID}, FieldOrder => $Param{FieldOrder}, Mode => 'Update', OldFieldOrder => $OldDynamicField->{FieldOrder}, ); } return 1; } =head2 DynamicFieldDelete() delete a Dynamic field entry. You need to make sure that all values are deleted before calling this function, otherwise it will fail on DBMS which check referential integrity. returns 1 if successful or undef otherwise my $Success = $DynamicFieldObject->DynamicFieldDelete( ID => 123, UserID => 123, Reorder => 1, # or 0, to trigger reorder function, default 1 ); =cut sub DynamicFieldDelete { my ( $Self, %Param ) = @_; # check needed stuff for my $Key (qw(ID UserID)) { if ( !$Param{$Key} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Key!" ); return; } } # check if exists my $DynamicField = $Self->DynamicFieldGet( ID => $Param{ID}, ); return if !IsHashRefWithData($DynamicField); # re-order before delete if ( !exists $Param{Reorder} || $Param{Reorder} ) { my $Success = $Self->_DynamicFieldReorder( ID => $DynamicField->{ID}, FieldOrder => $DynamicField->{FieldOrder}, Mode => 'Delete', ); } # delete dynamic field return if !$Kernel::OM->Get('Kernel::System::DB')->Do( SQL => 'DELETE FROM dynamic_field WHERE id = ?', Bind => [ \$Param{ID} ], ); # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # delete cache $CacheObject->CleanUp( Type => 'DynamicField', ); $CacheObject->CleanUp( Type => 'DynamicFieldValue', ); # trigger event $Self->EventHandler( Event => 'DynamicFieldDelete', Data => { NewData => $DynamicField, }, UserID => $Param{UserID}, ); return 1; } =head2 DynamicFieldList() get DynamicField list ordered by the the "Field Order" field in the DB my $List = $DynamicFieldObject->DynamicFieldList(); or my $List = $DynamicFieldObject->DynamicFieldList( Valid => 0, # optional, defaults to 1 # object type (optional) as STRING or as ARRAYREF ObjectType => 'Ticket', ObjectType => ['Ticket', 'Article'], ResultType => 'HASH', # optional, 'ARRAY' or 'HASH', defaults to 'ARRAY' FieldFilter => { # optional, only active fields (non 0) will be returned ItemOne => 1, ItemTwo => 2, ItemThree => 1, ItemFour => 1, ItemFive => 0, }, ); Returns: $List = { 1 => 'ItemOne', 2 => 'ItemTwo', 3 => 'ItemThree', 4 => 'ItemFour', }; or $List = ( 1, 2, 3, 4 ); =cut sub DynamicFieldList { my ( $Self, %Param ) = @_; # to store fieldIDs white-list my %AllowedFieldIDs; if ( defined $Param{FieldFilter} && ref $Param{FieldFilter} eq 'HASH' ) { # fill the fieldIDs white-list FIELDNAME: for my $FieldName ( sort keys %{ $Param{FieldFilter} } ) { next FIELDNAME if !$Param{FieldFilter}->{$FieldName}; my $FieldConfig = $Self->DynamicFieldGet( Name => $FieldName ); next FIELDNAME if !IsHashRefWithData($FieldConfig); next FIELDNAME if !$FieldConfig->{ID}; $AllowedFieldIDs{ $FieldConfig->{ID} } = 1; } } # check cache my $Valid = 1; if ( defined $Param{Valid} && $Param{Valid} eq '0' ) { $Valid = 0; } # set cache key object type component depending on the ObjectType parameter my $ObjectType = 'All'; if ( IsArrayRefWithData( $Param{ObjectType} ) ) { $ObjectType = join '_', sort @{ $Param{ObjectType} }; } elsif ( IsStringWithData( $Param{ObjectType} ) ) { $ObjectType = $Param{ObjectType}; } my $ResultType = $Param{ResultType} || 'ARRAY'; $ResultType = $ResultType eq 'HASH' ? 'HASH' : 'ARRAY'; # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); my $CacheKey = 'DynamicFieldList::Valid::' . $Valid . '::ObjectType::' . $ObjectType . '::ResultType::' . $ResultType; my $Cache = $CacheObject->Get( Type => 'DynamicField', Key => $CacheKey, ); if ($Cache) { # check if FieldFilter is not set if ( !defined $Param{FieldFilter} ) { # return raw data from cache return $Cache; } elsif ( ref $Param{FieldFilter} ne 'HASH' ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'FieldFilter must be a HASH reference!', ); return; } # otherwise apply the filter my $FilteredData; # check if cache is ARRAY ref if ( $ResultType eq 'ARRAY' ) { FIELDID: for my $FieldID ( @{$Cache} ) { next FIELDID if !$AllowedFieldIDs{$FieldID}; push @{$FilteredData}, $FieldID; } # return filtered data from cache return $FilteredData; } # otherwise is a HASH ref else { FIELDID: for my $FieldID ( sort keys %{$Cache} ) { next FIELDID if !$AllowedFieldIDs{$FieldID}; $FilteredData->{$FieldID} = $Cache->{$FieldID}; } } # return filtered data from cache return $FilteredData; } else { my $SQL = 'SELECT id, name, field_order FROM dynamic_field'; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); if ($Valid) { # get valid object my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid'); $SQL .= ' WHERE valid_id IN (' . join ', ', $ValidObject->ValidIDsGet() . ')'; if ( $Param{ObjectType} ) { if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) { $SQL .= " AND object_type = '" . $DBObject->Quote( $Param{ObjectType} ) . "'"; } elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) { my $ObjectTypeString = join ',', map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} }; $SQL .= " AND object_type IN ($ObjectTypeString)"; } } } else { if ( $Param{ObjectType} ) { if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) { $SQL .= " WHERE object_type = '" . $DBObject->Quote( $Param{ObjectType} ) . "'"; } elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) { my $ObjectTypeString = join ',', map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} }; $SQL .= " WHERE object_type IN ($ObjectTypeString)"; } } } $SQL .= " ORDER BY field_order, id"; return if !$DBObject->Prepare( SQL => $SQL ); if ( $ResultType eq 'HASH' ) { my %Data; while ( my @Row = $DBObject->FetchrowArray() ) { $Data{ $Row[0] } = $Row[1]; } # set cache $CacheObject->Set( Type => 'DynamicField', Key => $CacheKey, Value => \%Data, TTL => $Self->{CacheTTL}, ); # check if FieldFilter is not set if ( !defined $Param{FieldFilter} ) { # return raw data from DB return \%Data; } elsif ( ref $Param{FieldFilter} ne 'HASH' ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'FieldFilter must be a HASH reference!', ); return; } my %FilteredData; FIELDID: for my $FieldID ( sort keys %Data ) { next FIELDID if !$AllowedFieldIDs{$FieldID}; $FilteredData{$FieldID} = $Data{$FieldID}; } # return filtered data from DB return \%FilteredData; } else { my @Data; while ( my @Row = $DBObject->FetchrowArray() ) { push @Data, $Row[0]; } # set cache $CacheObject->Set( Type => 'DynamicField', Key => $CacheKey, Value => \@Data, TTL => $Self->{CacheTTL}, ); # check if FieldFilter is not set if ( !defined $Param{FieldFilter} ) { # return raw data from DB return \@Data; } elsif ( ref $Param{FieldFilter} ne 'HASH' ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'FieldFilter must be a HASH reference!', ); return; } my @FilteredData; FIELDID: for my $FieldID (@Data) { next FIELDID if !$AllowedFieldIDs{$FieldID}; push @FilteredData, $FieldID; } # return filtered data from DB return \@FilteredData; } } return; } =head2 DynamicFieldListGet() get DynamicField list with complete data ordered by the "Field Order" field in the DB my $List = $DynamicFieldObject->DynamicFieldListGet(); or my $List = $DynamicFieldObject->DynamicFieldListGet( Valid => 0, # optional, defaults to 1 # object type (optional) as STRING or as ARRAYREF ObjectType => 'Ticket', ObjectType => ['Ticket', 'Article'], FieldFilter => { # optional, only active fields (non 0) will be returned nameforfield => 1, fieldname => 2, other => 0, otherfield => 0, }, ); Returns: $List = ( { ID => 123, InternalField => 0, Name => 'nameforfield', Label => 'The label to show', FieldType => 'Text', ObjectType => 'Article', Config => $ConfigHashRef, ValidID => 1, CreateTime => '2011-02-08 15:08:00', ChangeTime => '2011-06-11 17:22:00', }, { ID => 321, InternalField => 0, Name => 'fieldname', Label => 'It is not a label', FieldType => 'Text', ObjectType => 'Ticket', Config => $ConfigHashRef, ValidID => 1, CreateTime => '2010-09-11 10:08:00', ChangeTime => '2011-01-01 01:01:01', }, ... ); =cut sub DynamicFieldListGet { my ( $Self, %Param ) = @_; # check cache my $Valid = 1; if ( defined $Param{Valid} && $Param{Valid} eq '0' ) { $Valid = 0; } # set cache key object type component depending on the ObjectType parameter my $ObjectType = 'All'; if ( IsArrayRefWithData( $Param{ObjectType} ) ) { $ObjectType = join '_', sort @{ $Param{ObjectType} }; } elsif ( IsStringWithData( $Param{ObjectType} ) ) { $ObjectType = $Param{ObjectType}; } # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); my $CacheKey = 'DynamicFieldListGet::Valid::' . $Valid . '::ObjectType::' . $ObjectType; my $Cache = $CacheObject->Get( Type => 'DynamicField', Key => $CacheKey, ); if ($Cache) { # check if FieldFilter is not set if ( !defined $Param{FieldFilter} ) { # return raw data from cache return $Cache; } elsif ( ref $Param{FieldFilter} ne 'HASH' ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'FieldFilter must be a HASH reference!', ); return; } my $FilteredData; DYNAMICFIELD: for my $DynamicFieldConfig ( @{$Cache} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; next DYNAMICFIELD if !$Param{FieldFilter}->{ $DynamicFieldConfig->{Name} }; push @{$FilteredData}, $DynamicFieldConfig; } # return filtered data from cache return $FilteredData; } # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); my @Data; my $SQL = 'SELECT id, name, field_order FROM dynamic_field'; if ($Valid) { # get valid object my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid'); $SQL .= ' WHERE valid_id IN (' . join ', ', $ValidObject->ValidIDsGet() . ')'; if ( $Param{ObjectType} ) { if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) { $SQL .= " AND object_type = '" . $DBObject->Quote( $Param{ObjectType} ) . "'"; } elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) { my $ObjectTypeString = join ',', map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} }; $SQL .= " AND object_type IN ($ObjectTypeString)"; } } } else { if ( $Param{ObjectType} ) { if ( IsStringWithData( $Param{ObjectType} ) && $Param{ObjectType} ne 'All' ) { $SQL .= " WHERE object_type = '" . $DBObject->Quote( $Param{ObjectType} ) . "'"; } elsif ( IsArrayRefWithData( $Param{ObjectType} ) ) { my $ObjectTypeString = join ',', map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{ObjectType} }; $SQL .= " WHERE object_type IN ($ObjectTypeString)"; } } } $SQL .= " ORDER BY field_order, id"; return if !$DBObject->Prepare( SQL => $SQL ); my @DynamicFieldIDs; while ( my @Row = $DBObject->FetchrowArray() ) { push @DynamicFieldIDs, $Row[0]; } for my $ItemID (@DynamicFieldIDs) { my $DynamicField = $Self->DynamicFieldGet( ID => $ItemID, ); push @Data, $DynamicField; } # set cache $CacheObject->Set( Type => 'DynamicField', Key => $CacheKey, Value => \@Data, TTL => $Self->{CacheTTL}, ); # check if FieldFilter is not set if ( !defined $Param{FieldFilter} ) { # return raw data from DB return \@Data; } elsif ( ref $Param{FieldFilter} ne 'HASH' ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'FieldFilter must be a HASH reference!', ); return; } my $FilteredData; DYNAMICFIELD: for my $DynamicFieldConfig (@Data) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; next DYNAMICFIELD if !$Param{FieldFilter}->{ $DynamicFieldConfig->{Name} }; push @{$FilteredData}, $DynamicFieldConfig; } # return filtered data from DB return $FilteredData; } =head2 DynamicFieldOrderReset() sets the order of all dynamic fields based on a consecutive number list starting with number 1. This function will remove duplicate order numbers and gaps in the numbering. my $Success = $DynamicFieldObject->DynamicFieldOrderReset(); Returns: $Success = 1; # or 0 in case of error =cut sub DynamicFieldOrderReset { my ( $Self, %Param ) = @_; # get all fields my $DynamicFieldList = $Self->DynamicFieldListGet( Valid => 0, ); # to set the field order my $Counter; # loop through all the dynamic fields DYNAMICFIELD: for my $DynamicField ( @{$DynamicFieldList} ) { # prepare the new field order $Counter++; # skip wrong fields (if any) next DYNAMICFIELD if !IsHashRefWithData($DynamicField); # skip fields with the correct order next DYNAMICFIELD if $DynamicField->{FieldOrder} eq $Counter; $DynamicField->{FieldOrder} = $Counter; # update the database my $Success = $Self->DynamicFieldUpdate( %{$DynamicField}, UserID => 1, Reorder => 0, ); # check if the update was successful if ( !$Success ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'An error was detected while re ordering the field list on field ' . "DynamicField->{Name}!", ); return; } } return 1; } =head2 DynamicFieldOrderCheck() checks for duplicate order numbers and gaps in the numbering. my $Success = $DynamicFieldObject->DynamicFieldOrderCheck(); Returns: $Success = 1; # or 0 in case duplicates or gaps in the dynamic fields # order numbering =cut sub DynamicFieldOrderCheck { my ( $Self, %Param ) = @_; # get all fields my $DynamicFieldList = $Self->DynamicFieldListGet( Valid => 0, ); # to had a correct order reference my $Counter; # flag to be activated if the order is not correct my $OrderError; # loop through all the dynamic fields DYNAMICFIELD: for my $DynamicField ( @{$DynamicFieldList} ) { $Counter++; # skip wrong fields (if any) next DYNAMICFIELD if !IsHashRefWithData($DynamicField); # skip fields with correct order next DYNAMICFIELD if $DynamicField->{FieldOrder} eq $Counter; # when finding a field with wrong order, set OrderError flag and exit loop $OrderError = 1; last DYNAMICFIELD; } return if $OrderError; return 1; } =head2 ObjectMappingGet() (a) Fetches object ID(s) for given object name(s). (b) Fetches object name(s) for given object ID(s). NOTE: Only use object mappings for dynamic fields that must support non-integer object IDs, like customer user logins and customer company IDs. my $ObjectMapping = $DynamicFieldObject->ObjectMappingGet( ObjectName => $ObjectName, # Name or array ref of names of the object(s) to get the ID(s) for # Note: either give ObjectName or ObjectID ObjectID => $ObjectID, # ID or array ref of IDs of the object(s) to get the name(s) for # Note: either give ObjectName or ObjectID ObjectType => 'CustomerUser', # Type of object to get mapping for ); Returns for parameter ObjectID: $ObjectMapping = { ObjectID => ObjectName, ObjectID => ObjectName, ObjectID => ObjectName, # ... }; Returns for parameter ObjectName: $ObjectMapping = { ObjectName => ObjectID, ObjectName => ObjectID, ObjectName => ObjectID, # ... }; =cut sub ObjectMappingGet { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw( ObjectType )) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!" ); return; } } if ( $Param{ObjectName} && $Param{ObjectID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Either give parameter ObjectName or ObjectID, not both." ); return; } if ( !$Param{ObjectName} && !$Param{ObjectID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "You have to give parameter ObjectName or ObjectID." ); return; } # Get config object my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); # Get configuration for this object type my $Config = $ConfigObject->Get("DynamicFields::ObjectType") || {}; my $ObjecTypesConfig = $Config->{ $Param{ObjectType} }; if ( !IsHashRefWithData($ObjecTypesConfig) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Configuration for dynamic field object type $Param{ObjectType} is invalid!", ); return; } if ( !$ObjecTypesConfig->{UseObjectName} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Dynamic field object type $Param{ObjectType} does not support this function", ); return; } my $Type = $Param{ObjectName} ? 'ObjectName' : 'ObjectID'; if ( !IsArrayRefWithData( $Param{$Type} ) ) { $Param{$Type} = [ $Param{$Type}, ]; } my %LookupValues = map { $_ => '?' } @{ $Param{$Type} }; my $CacheKey = 'ObjectMappingGet::' . $Type . '::' . ( join ',', sort keys %LookupValues ) . '::' . $Param{ObjectType}; my $CacheType = 'DynamicFieldObjectMapping' . $Type; # Get cache object. my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); my $Cache = $CacheObject->Get( Type => $CacheType, Key => $CacheKey, ); return $Cache if IsHashRefWithData($Cache); my $SQL; if ( $Type eq 'ObjectID' ) { $SQL = ' SELECT object_id, object_name FROM dynamic_field_obj_id_name WHERE object_id IN (' . ( join ', ', values %LookupValues ) . ') AND object_type = ?'; } else { $SQL = ' SELECT object_name, object_id FROM dynamic_field_obj_id_name WHERE object_name IN (' . ( join ', ', values %LookupValues ) . ') AND object_type = ?'; } my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); return if !$DBObject->Prepare( SQL => $SQL, Bind => [ \keys %LookupValues, \$Param{ObjectType}, ], ); my %ObjectMapping; while ( my @Data = $DBObject->FetchrowArray() ) { $ObjectMapping{ $Data[0] } = $Data[1]; } # set cache my $CacheTTL = $ConfigObject->Get('DynamicField::CacheTTL') || 60 * 60 * 12; $CacheObject->Set( Type => $CacheType, Key => $CacheKey, Value => \%ObjectMapping, TTL => $CacheTTL, ); return \%ObjectMapping; } =head2 ObjectMappingCreate() Creates an object mapping for the given given object name. NOTE: Only use object mappings for dynamic fields that must support non-integer object IDs, like customer user logins and customer company IDs. my $ObjectID = $DynamicFieldObject->ObjectMappingCreate( ObjectName => 'customer-1', # Name of the object to create the mapping for ObjectType => 'CustomerUser', # Type of object to create the mapping for ); =cut sub ObjectMappingCreate { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw( ObjectName ObjectType )) { if ( !defined $Param{$Needed} || !length $Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!" ); return; } } # Get configuration for this object type my $Config = $Kernel::OM->Get('Kernel::Config')->Get("DynamicFields::ObjectType") || {}; my $ObjecTypesConfig = $Config->{ $Param{ObjectType} }; if ( !IsHashRefWithData($ObjecTypesConfig) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Configuration for dynamic field object type $Param{ObjectType} is invalid!", ); return; } if ( !$ObjecTypesConfig->{UseObjectName} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Dynamic field object type $Param{ObjectType} does not support this function", ); return; } my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); return if !$DBObject->Do( SQL => ' INSERT INTO dynamic_field_obj_id_name (object_name, object_type) VALUES (?, ?)', Bind => [ \$Param{ObjectName}, \$Param{ObjectType}, ], ); return if !$DBObject->Prepare( SQL => ' SELECT object_id FROM dynamic_field_obj_id_name WHERE object_name = ? AND object_type = ?', Bind => [ \$Param{ObjectName}, \$Param{ObjectType}, ], Limit => 1, ); my $ObjectID; while ( my @Data = $DBObject->FetchrowArray() ) { $ObjectID = $Data[0]; } return $ObjectID; } =head2 ObjectMappingNameChange() Changes name of given object mapping. NOTE: Only use object mappings for dynamic fields that must support non-integer object IDs, like customer user logins and customer company IDs. my $Success = $DynamicFieldObject->ObjectMappingNameChange( OldObjectName => 'customer-1', NewObjectName => 'customer-2', ObjectType => 'CustomerUser', # Type of object to change name for ); Returns 1 on success. =cut sub ObjectMappingNameChange { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw( OldObjectName NewObjectName ObjectType )) { if ( !defined $Param{$Needed} || !length $Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!" ); return; } } # Get configuration for this object type my $Config = $Kernel::OM->Get('Kernel::Config')->Get("DynamicFields::ObjectType") || {}; my $ObjecTypesConfig = $Config->{ $Param{ObjectType} }; if ( !IsHashRefWithData($ObjecTypesConfig) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Configuration for dynamic field object type $Param{ObjectType} is invalid!", ); return; } if ( !$ObjecTypesConfig->{UseObjectName} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Dynamic field object type $Param{ObjectType} does not support this function", ); return; } my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); return if !$DBObject->Do( SQL => ' UPDATE dynamic_field_obj_id_name SET object_name = ? WHERE object_name = ? AND object_type = ?', Bind => [ \$Param{NewObjectName}, \$Param{OldObjectName}, \$Param{ObjectType}, ], ); # Clean up cache for type DynamicFieldValueObjectName. # A cleanup based on the changed object name is not possible. my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); $CacheObject->CleanUp( Type => 'DynamicFieldObjectMappingObjectID', ); $CacheObject->CleanUp( Type => 'DynamicFieldObjectMappingObjectName', ); return 1; } sub DESTROY { my $Self = shift; # execute all transaction events $Self->EventHandlerTransaction(); return 1; } =begin Internal: =cut =head2 _DynamicFieldReorder() re-order the list of fields. $Success = $DynamicFieldObject->_DynamicFieldReorder( ID => 123, # mandatory, the field ID that triggers the re-order Mode => 'Add', # || Update || Delete FieldOrder => 2, # mandatory, the FieldOrder from the trigger field ); $Success = $DynamicFieldObject->_DynamicFieldReorder( ID => 123, # mandatory, the field ID that triggers the re-order Mode => 'Update', # || Update || Delete FieldOrder => 2, # mandatory, the FieldOrder from the trigger field OldFieldOrder => 10, # mandatory for Mode = 'Update', the FieldOrder before the # update ); =cut sub _DynamicFieldReorder { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(ID FieldOrder Mode)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need $Needed!' ); return; } } if ( $Param{Mode} eq 'Update' ) { # check needed stuff if ( !$Param{OldFieldOrder} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need OldFieldOrder!' ); return; } } # get the Dynamic Field trigger my $DynamicFieldTrigger = $Self->DynamicFieldGet( ID => $Param{ID}, ); # extract the field order from the params my $TriggerFieldOrder = $Param{FieldOrder}; # get all fields my $DynamicFieldList = $Self->DynamicFieldListGet( Valid => 0, ); # to store the fields that need to be updated my @NeedToUpdateList; # to add or subtract the field order by 1 my $Substract; # update and add has different algorithms to select the fields to be updated # check if update if ( $Param{Mode} eq 'Update' ) { my $OldFieldOrder = $Param{OldFieldOrder}; # if the new order and the old order are equal no operation should be performed # this is a double check from DynamicFieldUpdate (is case of the function is called # from outside) return if $TriggerFieldOrder eq $OldFieldOrder; # set subtract mode for selected fields if ( $TriggerFieldOrder > $OldFieldOrder ) { $Substract = 1; } # identify fields that needs to be updated DYNAMICFIELD: for my $DynamicField ( @{$DynamicFieldList} ) { # skip wrong fields (if any) next DYNAMICFIELD if !IsHashRefWithData($DynamicField); my $CurrentOrder = $DynamicField->{FieldOrder}; # skip fields with lower order number next DYNAMICFIELD if $CurrentOrder < $OldFieldOrder && $CurrentOrder < $TriggerFieldOrder; # skip trigger field next DYNAMICFIELD if ( $CurrentOrder eq $TriggerFieldOrder && $DynamicField->{ID} eq $Param{ID} ); # skip this and the rest if has greater order number last DYNAMICFIELD if $CurrentOrder > $OldFieldOrder && $CurrentOrder > $TriggerFieldOrder; push @NeedToUpdateList, $DynamicField; } } # check if delete action elsif ( $Param{Mode} eq 'Delete' ) { $Substract = 1; # identify fields that needs to be updated DYNAMICFIELD: for my $DynamicField ( @{$DynamicFieldList} ) { # skip wrong fields (if any) next DYNAMICFIELD if !IsHashRefWithData($DynamicField); my $CurrentOrder = $DynamicField->{FieldOrder}; # skip fields with lower order number next DYNAMICFIELD if $CurrentOrder < $TriggerFieldOrder; # skip trigger field next DYNAMICFIELD if ( $CurrentOrder eq $TriggerFieldOrder && $DynamicField->{ID} eq $Param{ID} ); push @NeedToUpdateList, $DynamicField; } } # otherwise is add action else { # identify fields that needs to be updated DYNAMICFIELD: for my $DynamicField ( @{$DynamicFieldList} ) { # skip wrong fields (if any) next DYNAMICFIELD if !IsHashRefWithData($DynamicField); my $CurrentOrder = $DynamicField->{FieldOrder}; # skip fields with lower order number next DYNAMICFIELD if $CurrentOrder < $TriggerFieldOrder; # skip trigger field next DYNAMICFIELD if ( $CurrentOrder eq $TriggerFieldOrder && $DynamicField->{ID} eq $Param{ID} ); push @NeedToUpdateList, $DynamicField; } } # update the fields order incrementing or decrementing by 1 for my $DynamicField (@NeedToUpdateList) { # hash ref validation is not needed since it was validated before # check if need to add or subtract if ($Substract) { # subtract 1 to the dynamic field order value $DynamicField->{FieldOrder}--; } else { # add 1 to the dynamic field order value $DynamicField->{FieldOrder}++; } # update the database my $Success = $Self->DynamicFieldUpdate( %{$DynamicField}, UserID => 1, Reorder => 0, ); # check if the update was successful if ( !$Success ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'An error was detected while re ordering the field list on field ' . "DynamicField->{Name}!", ); return; } } # delete cache $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( Type => 'DynamicField', ); return 1; } =end Internal: =head1 TERMS AND CONDITIONS This software is part of the OTRS project (L). This software comes with ABSOLUTELY NO WARRANTY. For details, see the enclosed file COPYING for license information (GPL). If you did not receive this file, see L. =cut 1;