# --
# 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;