# --
# 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::ITSMChange::ITSMCondition::Expression;
use strict;
use warnings;
our $ObjectManagerDisabled = 1;
=head1 NAME
Kernel::System::ITSMChange::ITSMCondition::Expression - condition expression lib
=head1 PUBLIC INTERFACE
=head2 ExpressionAdd()
Add a new condition expression.
my $ExpressionID = $ConditionObject->ExpressionAdd(
ConditionID => 123,
ObjectID => 234,
AttributeID => 345,
OperatorID => 456,
Selector => 1234,
CompareValue => 'rejected',
UserID => 1,
);
=cut
sub ExpressionAdd {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ConditionID ObjectID AttributeID OperatorID Selector UserID)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# handle 'CompareValue' in a special way
if ( !exists $Param{CompareValue} || !defined $Param{CompareValue} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need CompareValue!',
);
return;
}
# get condition for event handler
my $Condition = $Self->ConditionGet(
ConditionID => $Param{ConditionID},
UserID => $Param{UserID},
);
# check condition
return if !$Condition;
# trigger ExpressionAddPre-Event
$Self->EventHandler(
Event => 'ExpressionAddPre',
Data => {
%Param,
ChangeID => $Condition->{ChangeID},
},
UserID => $Param{UserID},
);
# add new expression name to database
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => 'INSERT INTO condition_expression '
. '(condition_id, object_id, attribute_id, '
. 'operator_id, selector, compare_value) '
. 'VALUES (?, ?, ?, ?, ?, ?)',
Bind => [
\$Param{ConditionID}, \$Param{ObjectID}, \$Param{AttributeID},
\$Param{OperatorID}, \$Param{Selector}, \$Param{CompareValue},
],
);
# prepare SQL statement
my $ExpressionID;
# this is important for oracle for which an empty string and NULL is the same!
if ( $Self->{DBType} eq 'oracle' && $Param{CompareValue} eq '' ) {
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => 'SELECT id FROM condition_expression '
. 'WHERE condition_id = ? AND object_id = ? AND attribute_id = ? '
. 'AND operator_id = ? AND selector = ? AND compare_value IS NULL',
Bind => [
\$Param{ConditionID}, \$Param{ObjectID}, \$Param{AttributeID},
\$Param{OperatorID}, \$Param{Selector},
],
Limit => 1,
);
}
# for all other databases AND for oracle IF the compare value is NOT an empty string
else {
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => 'SELECT id FROM condition_expression '
. 'WHERE condition_id = ? AND object_id = ? AND attribute_id = ? '
. 'AND operator_id = ? AND selector = ? AND compare_value = ?',
Bind => [
\$Param{ConditionID}, \$Param{ObjectID}, \$Param{AttributeID},
\$Param{OperatorID}, \$Param{Selector}, \$Param{CompareValue},
],
Limit => 1,
);
}
# get id of created expression
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
$ExpressionID = $Row[0];
}
# check if expression could be added
if ( !$ExpressionID ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "ExpressionAdd() failed!",
);
return;
}
# delete cache
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
Type => $Self->{CacheType},
Key => 'ExpressionList::ConditionID::' . $Param{ConditionID},
);
# trigger ExpressionAddPost-Event
$Self->EventHandler(
Event => 'ExpressionAddPost',
Data => {
%Param,
ChangeID => $Condition->{ChangeID},
ExpressionID => $ExpressionID,
},
UserID => $Param{UserID},
);
return $ExpressionID;
}
=head2 ExpressionUpdate()
Update a condition expression.
my $Success = $ConditionObject->ExpressionUpdate(
ExpressionID => 1234,
ObjectID => 234, # (optional)
AttributeID => 345, # (optional)
OperatorID => 456, # (optional)
Selector => 1234, # (optional)
CompareValue => 'rejected', # (optional)
UserID => 1,
);
=cut
sub ExpressionUpdate {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ExpressionID UserID)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# get expression
my $Expression = $Self->ExpressionGet(
ExpressionID => $Param{ExpressionID},
UserID => $Param{UserID},
);
# check expression
return if !$Expression;
# get condition for event handler
my $Condition = $Self->ConditionGet(
ConditionID => $Expression->{ConditionID},
UserID => $Param{UserID},
);
# check condition
return if !$Condition;
# trigger ExpressionUpdatePre-Event
$Self->EventHandler(
Event => 'ExpressionUpdatePre',
Data => {
%Param,
ChangeID => $Condition->{ChangeID},
},
UserID => $Param{UserID},
);
# map update attributes to column names
my %Attribute = (
ObjectID => 'object_id',
AttributeID => 'attribute_id',
OperatorID => 'operator_id',
Selector => 'selector',
CompareValue => 'compare_value',
);
# build SQL to update expression
my $SQL = 'UPDATE condition_expression SET ';
my @Bind;
ATTRIBUTE:
for my $Attribute ( sort keys %Attribute ) {
# preserve the old value, when the column isn't in function parameters
next ATTRIBUTE if !exists $Param{$Attribute};
next ATTRIBUTE if !defined $Param{$Attribute};
# param checking has already been done, so this is safe
$SQL .= "$Attribute{$Attribute} = ?, ";
push @Bind, \$Param{$Attribute};
}
# set condition ID to allow trailing comma of previous loop
$SQL .= ' condition_id = condition_id ';
# set matching of SQL statement
$SQL .= 'WHERE id = ?';
push @Bind, \$Param{ExpressionID};
# update expression
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => $SQL,
Bind => \@Bind,
);
# delete cache
for my $Key (
'ExpressionList::ConditionID::' . $Expression->{ConditionID},
'ExpressionGet::' . $Param{ExpressionID},
)
{
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
Type => $Self->{CacheType},
Key => $Key,
);
}
# trigger ExpressionUpdatePost-Event
$Self->EventHandler(
Event => 'ExpressionUpdatePost',
Data => {
%Param,
ChangeID => $Condition->{ChangeID},
OldExpressionData => $Expression,
},
UserID => $Param{UserID},
);
return 1;
}
=head2 ExpressionGet()
Get a condition expression for a given expression id.
Returns a hash reference of the expression data.
my $ConditionExpressionRef = $ConditionObject->ExpressionGet(
ExpressionID => 1234,
UserID => 1,
);
The returned hash reference contains following elements:
$ConditionExpression{ExpressionID}
$ConditionExpression{ConditionID}
$ConditionExpression{ObjectID}
$ConditionExpression{AttributeID}
$ConditionExpression{OperatorID}
$ConditionExpression{Selector}
$ConditionExpression{CompareValue}
=cut
sub ExpressionGet {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ExpressionID UserID)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# check cache
my $CacheKey = 'ExpressionGet::' . $Param{ExpressionID};
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
Type => $Self->{CacheType},
Key => $CacheKey,
);
return $Cache if $Cache;
# prepare SQL statement
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => 'SELECT id, condition_id, object_id, attribute_id, '
. 'operator_id, selector, compare_value '
. 'FROM condition_expression WHERE id = ?',
Bind => [ \$Param{ExpressionID} ],
Limit => 1,
);
# fetch the result
my %ExpressionData;
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
$ExpressionData{ExpressionID} = $Row[0];
$ExpressionData{ConditionID} = $Row[1];
$ExpressionData{ObjectID} = $Row[2];
$ExpressionData{AttributeID} = $Row[3];
$ExpressionData{OperatorID} = $Row[4];
$ExpressionData{Selector} = $Row[5];
# this is important for oracle for which an empty string and NULL is the same!
$ExpressionData{CompareValue} = $Row[6] // '';
}
# check error
if ( !%ExpressionData ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "ExpressionID $Param{ExpressionID} does not exist!",
);
return;
}
# set cache
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $Self->{CacheType},
Key => $CacheKey,
Value => \%ExpressionData,
TTL => $Self->{CacheTTL},
);
return \%ExpressionData;
}
=head2 ExpressionList()
Returns a list of all condition expression ids for
a given ConditionID as array reference.
my $ConditionExpressionIDsRef = $ConditionObject->ExpressionList(
ConditionID => 1234,
UserID => 1,
);
=cut
sub ExpressionList {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ConditionID UserID)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# check cache
my $CacheKey = 'ExpressionList::ConditionID::' . $Param{ConditionID};
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
Type => $Self->{CacheType},
Key => $CacheKey,
);
return $Cache if $Cache;
# prepare SQL statement
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => 'SELECT id FROM condition_expression '
. 'WHERE condition_id = ?',
Bind => [ \$Param{ConditionID} ],
);
# fetch the result
my @ExpressionList;
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
push @ExpressionList, $Row[0];
}
# set cache
$Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $Self->{CacheType},
Key => $CacheKey,
Value => \@ExpressionList,
TTL => $Self->{CacheTTL},
);
return \@ExpressionList;
}
=head2 ExpressionDelete()
Deletes a condition expression.
my $Success = $ConditionObject->ExpressionDelete(
ExpressionID => 123,
UserID => 1,
);
=cut
sub ExpressionDelete {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ExpressionID UserID)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# get expression
my $Expression = $Self->ExpressionGet(
ExpressionID => $Param{ExpressionID},
UserID => $Param{UserID},
);
# check expression
return if !$Expression;
# get condition for event handler
my $Condition = $Self->ConditionGet(
ConditionID => $Expression->{ConditionID},
UserID => $Param{UserID},
);
# check condition
return if !$Condition;
# trigger ExpressionDeletePre-Event
$Self->EventHandler(
Event => 'ExpressionDeletePre',
Data => {
%Param,
ChangeID => $Condition->{ChangeID},
},
UserID => $Param{UserID},
);
# delete condition expression from database
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => 'DELETE FROM condition_expression '
. 'WHERE id = ?',
Bind => [ \$Param{ExpressionID} ],
);
# delete cache
for my $Key (
'ExpressionList::ConditionID::' . $Expression->{ConditionID},
'ExpressionGet::' . $Param{ExpressionID},
)
{
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
Type => $Self->{CacheType},
Key => $Key,
);
}
# trigger ExpressionDeletePost-Event
$Self->EventHandler(
Event => 'ExpressionDeletePost',
Data => {
%Param,
ChangeID => $Condition->{ChangeID},
},
UserID => $Param{UserID},
);
return 1;
}
=head2 ExpressionDeleteAll()
Deletes all condition expressions for a given condition id.
my $Success = $ConditionObject->ExpressionDeleteAll(
ConditionID => 123,
UserID => 1,
);
=cut
sub ExpressionDeleteAll {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ConditionID UserID)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# get condition for event handler
my $Condition = $Self->ConditionGet(
ConditionID => $Param{ConditionID},
UserID => $Param{UserID},
);
# check condition
return if !$Condition;
# get all expressions for the given condition id
my $ExpressionIDsRef = $Self->ExpressionList(
ConditionID => $Param{ConditionID},
UserID => $Param{UserID},
);
# trigger ExpressionDeleteAllPre-Event
$Self->EventHandler(
Event => 'ExpressionDeleteAllPre',
Data => {
%Param,
ChangeID => $Condition->{ChangeID},
ConditionID => $Param{ConditionID},
},
UserID => $Param{UserID},
);
# delete condition expressions from database
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => 'DELETE FROM condition_expression '
. 'WHERE condition_id = ?',
Bind => [ \$Param{ConditionID} ],
);
# delete cache
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
Type => $Self->{CacheType},
Key => 'ExpressionList::ConditionID::' . $Param{ConditionID},
);
# delete cache
if ( $ExpressionIDsRef && @{$ExpressionIDsRef} ) {
for my $ExpressionID ( @{$ExpressionIDsRef} ) {
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
Type => $Self->{CacheType},
Key => 'ExpressionGet::' . $ExpressionID,
);
}
}
# trigger ExpressionDeleteAllPost-Event
$Self->EventHandler(
Event => 'ExpressionDeleteAllPost',
Data => {
%Param,
ChangeID => $Condition->{ChangeID},
ConditionID => $Param{ConditionID},
},
UserID => $Param{UserID},
);
return 1;
}
=head2 ExpressionMatch()
Returns the boolean value of an expression.
my $Match = $ConditionObject->ExpressionMatch(
ExpressionID => 123,
AttributesChanged => { ITSMChange => [ ChangeTitle, ChangeDescription ] }, # (optional)
UserID => 1,
);
=cut
sub ExpressionMatch {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ExpressionID UserID)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# get expression content
my $Expression = $Self->ExpressionGet(
ExpressionID => $Param{ExpressionID},
UserID => $Param{UserID},
);
# check expression content
return if !$Expression;
# get expression attributes
my $ExpressionData = $Self->_ExpressionMatchInit(
Expression => $Expression,
UserID => $Param{UserID},
);
# check expression attributes
return if !$ExpressionData;
# get changed attributes
my $AttributesChangedType;
my @AttributesChanged;
if ( exists $Param{AttributesChanged} && defined $Param{AttributesChanged} ) {
# changed attributes
my %AttributeChanged;
# check for reference type
if ( ref $Param{AttributesChanged} eq 'HASH' ) {
%AttributeChanged = %{ $Param{AttributesChanged} };
# get attribute type
$AttributesChangedType = ( keys %AttributeChanged )[0];
}
# check for reference type
if ( $AttributesChangedType && ref $AttributeChanged{$AttributesChangedType} eq 'ARRAY' ) {
# get list of changed attributes
@AttributesChanged = @{ $AttributeChanged{$AttributesChangedType} };
}
}
# get object name
my $ObjectName = $ExpressionData->{Object}->{Name};
# check for changed attributes types
if ( $AttributesChangedType && $AttributesChangedType ne $ObjectName ) {
# this expression does not match requested type
return;
}
# get attribute type
my $AttributeType = $ExpressionData->{Attribute}->{Name};
# check for changed attributes and available attributes of expression
if ( $AttributeType && @AttributesChanged ) {
# check for our attribute in changed attribute list
my @AttributeFound = grep { $_ eq $AttributeType } @AttributesChanged;
# this expression does not have the requested attribute
return if !@AttributeFound;
}
# get object data
my $ExpressionObjectData = $Self->ObjectDataGet(
ConditionID => $Expression->{ConditionID},
ObjectName => $ObjectName,
Selector => $Expression->{Selector},
UserID => $Param{UserID},
);
# check for expression object data
# no need to execute operator if it is an empty array ref
if (
!$ExpressionObjectData
|| ref $ExpressionObjectData ne 'ARRAY'
|| ref $ExpressionObjectData eq 'ARRAY' && !@{$ExpressionObjectData}
)
{
return;
}
# check attribute type
if ( !$AttributeType ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No attribute $ObjectName ($Expression->{Selector}) found!",
);
return;
}
# check for object attribute
for my $ExpressionObject ( @{$ExpressionObjectData} ) {
if ( !exists $ExpressionObject->{$AttributeType} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No object attribute for $ObjectName ($AttributeType) found!",
);
return;
}
}
# return result of the expressions execution
my $Result = $Self->OperatorExecute(
OperatorName => $ExpressionData->{Operator}->{Name},
Attribute => $AttributeType,
Selector => $Expression->{Selector},
ObjectData => $ExpressionObjectData,
CompareValue => $Expression->{CompareValue},
UserID => $Param{UserID},
);
# return result of the expressions execution
return $Result;
}
=head1 PRIVATE INTERFACE
=head2 _ExpressionMatchInit()
Returns object, attribute and operator of a given expression.
my $ExpressionData = $ConditionObject->_ExpressionMatchInit(
Expression => $ExpressionRef,
UserID => 1,
);
=cut
sub _ExpressionMatchInit {
my ( $Self, %Param ) = @_;
# extract expression
my $Expression = $Param{Expression};
# declare expression data
my %ExpressionData;
# get object data
$ExpressionData{Object} = $Self->ObjectGet(
ObjectID => $Expression->{ObjectID},
UserID => $Param{UserID},
);
# check for object data
if ( !$ExpressionData{Object} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No value for 'Object' with ID '$Expression->{ObjectID}'!",
);
return;
}
# get attribute data
$ExpressionData{Attribute} = $Self->AttributeGet(
AttributeID => $Expression->{AttributeID},
UserID => $Param{UserID},
);
# check for attribute data
if ( !$ExpressionData{Attribute} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No value for 'Attribute' with ID '$Expression->{AttributeID}'!",
);
return;
}
# get operator data
$ExpressionData{Operator} = $Self->OperatorGet(
OperatorID => $Expression->{OperatorID},
UserID => $Param{UserID},
);
# check for operator data
if ( !$ExpressionData{Operator} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No value for 'Operator' with ID '$Expression->{OperatorID}'!",
);
return;
}
return \%ExpressionData;
}
1;
=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