Files
scripts/Perl OTRS/Kernel/System/DynamicField.pm
2024-10-14 00:08:40 +02:00

1703 lines
48 KiB
Perl

# --
# 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<https://otrs.org/>).
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<https://www.gnu.org/licenses/gpl-3.0.txt>.
=cut
1;