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

1101 lines
43 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::ProcessManagement::Transition;
use strict;
use warnings;
use Kernel::System::VariableCheck qw(:all);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Log',
'Kernel::System::Main',
);
=head1 NAME
Kernel::System::ProcessManagement::Transition - Transition lib
=head1 DESCRIPTION
All Process Management Transition functions.
=head1 PUBLIC INTERFACE
=head2 new()
Don't use the constructor directly, use the ObjectManager instead:
my $TransitionObject = $Kernel::OM->Get('Kernel::System::ProcessManagement::Transition');
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# get config object
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# get the debug parameters
$Self->{TransitionDebug} = $ConfigObject->Get('ProcessManagement::Transition::Debug::Enabled') || 0;
$Self->{TransitionDebugLogPriority}
= $ConfigObject->Get('ProcessManagement::Transition::Debug::LogPriority') || 'debug';
my $TransitionDebugConfigFilters = $ConfigObject->Get('ProcessManagement::Transition::Debug::Filter') || {};
for my $FilterName ( sort keys %{$TransitionDebugConfigFilters} ) {
my %Filter = %{ $TransitionDebugConfigFilters->{$FilterName} };
for my $FilterItem ( sort keys %Filter ) {
$Self->{TransitionDebugFilters}->{$FilterItem} = $Filter{$FilterItem};
}
}
return $Self;
}
=head2 TransitionGet()
Get Transition info
my $Transition = $TransitionObject->TransitionGet(
TransitionEntityID => 'T1',
);
Returns:
$Transition = {
Name => 'Transition 1',
CreateTime => '08-02-2012 13:37:00',
ChangeBy => '2',
ChangeTime => '09-02-2012 13:37:00',
CreateBy => '3',
Condition => {
Type => 'and',
Cond1 => {
Type => 'and',
Fields => {
DynamicField_Make => [ '2' ],
DynamicField_VWModel => {
Type => 'String',
Match => 'Golf',
},
DynamicField_A => {
Type => 'Hash',
Match => {
Value => 1,
},
},
DynamicField_B => {
Type => 'Regexp',
Match => qr{ [\n\r\f] }xms
},
DynamicField_C => {
Type => 'Module',
Match =>
'Kernel::System::ProcessManagement::TransitionValidation::MyModule',
},
Queue => {
Type => 'Array',
Match => [ 'Raw' ],
},
# ...
}
}
# ...
},
};
=cut
sub TransitionGet {
my ( $Self, %Param ) = @_;
for my $Needed (qw(TransitionEntityID)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my $Transition = $Kernel::OM->Get('Kernel::Config')->Get('Process::Transition');
if ( !IsHashRefWithData($Transition) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need Transition config!',
);
return;
}
if ( !IsHashRefWithData( $Transition->{ $Param{TransitionEntityID} } ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No data for Transition '$Param{TransitionEntityID}' found!",
);
return;
}
return $Transition->{ $Param{TransitionEntityID} };
}
=head2 TransitionCheck()
Checks if one or more Transition Conditions are true
my $TransitionCheck = $TransitionObject->TransitionCheck(
TransitionEntityID => 'T1',
or
TransitionEntityID => ['T1', 'T2', 'T3'],
Data => {
Queue => 'Raw',
DynamicField1 => 'Value',
Subject => 'Testsubject',
...
},
);
If called on a single TransitionEntityID
Returns:
$Checked = 1; # 0
If called on an array of TransitionEntityIDs
Returns:
$Checked = 'T1' # 0 if no Transition was true
=cut
sub TransitionCheck {
my ( $Self, %Param ) = @_;
# Check if we have TransitionEntityID and Data.
for my $Needed (qw(TransitionEntityID Data)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# Check if TransitionEntityID is not empty (either Array or String with length).
if ( !length $Param{TransitionEntityID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need TransitionEntityID or TransitionEntityID array!",
);
return;
}
# Check if debug filters apply (ticket).
if ( $Self->{TransitionDebug} ) {
DEBUGFILTER:
for my $DebugFilter ( sort keys %{ $Self->{TransitionDebugFilters} } ) {
next DEBUGFILTER if $DebugFilter eq 'TransitionEntityID';
next DEBUGFILTER if !$Self->{TransitionDebugFilters}->{$DebugFilter};
next DEBUGFILTER if ref $Param{Data} ne 'HASH';
if ( $DebugFilter =~ m{<OTRS_TICKET_([^>]+)>}msx ) {
my $TicketParam = $1;
if (
defined $Param{Data}->{$TicketParam}
&& $Param{Data}->{$TicketParam}
)
{
if ( ref $Param{Data}->{$TicketParam} eq 'ARRAY' ) {
for my $Item ( @{ $Param{Data}->{$TicketParam} } ) {
# If matches for one item go to next filter (debug keeps active).
if ( $Self->{TransitionDebugFilters}->{$DebugFilter} eq $Item ) {
next DEBUGFILTER;
}
}
# If no matches then deactivate debug.
$Self->{TransitionDebug} = 0;
last DEBUGFILTER;
}
elsif (
$Self->{TransitionDebugFilters}->{$DebugFilter} ne
$Param{Data}->{$TicketParam}
)
{
$Self->{TransitionDebug} = 0;
last DEBUGFILTER;
}
elsif ( !defined $Param{Data}->{$TicketParam} ) {
$Self->{TransitionDebug} = 0;
last DEBUGFILTER;
}
}
}
}
}
# If we got just a string, make $Param{TransitionEntityID} an Array with the string on position
# 0 to facilitate handling
if ( !IsArrayRefWithData( $Param{TransitionEntityID} ) ) {
$Param{TransitionEntityID} = [ $Param{TransitionEntityID} ];
}
# Check if we have Data to check against transitions conditions.
if ( !IsHashRefWithData( $Param{Data} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Data has no values!",
);
return;
}
# Get all transitions.
my $Transitions = $Kernel::OM->Get('Kernel::Config')->Get('Process::Transition');
# Check if there are Transitions.
if ( !IsHashRefWithData($Transitions) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need transition config!',
);
return;
}
$Self->{TransitionDebugOrig} = $Self->{TransitionDebug};
# Loop through all submitted TransitionEntityID's.
TRANSITIONENTITYID:
for my $TransitionEntityID ( @{ $Param{TransitionEntityID} } ) {
$Self->{TransitionDebug} = $Self->{TransitionDebugOrig};
# Check if debug filters apply (Transition) (only if TransitionDebug is active).
if (
$Self->{TransitionDebug}
&& defined $Self->{TransitionDebugFilters}->{'TransitionEntityID'}
&& $Self->{TransitionDebugFilters}->{'TransitionEntityID'} ne $TransitionEntityID
)
{
$Self->{TransitionDebug} = 0;
}
# Check if the submitted TransitionEntityID has a config.
if ( !IsHashRefWithData( $Transitions->{$TransitionEntityID} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No config data for transition $TransitionEntityID found!",
);
return;
}
# Check if we have TransitionConditions.
if ( !IsHashRefWithData( $Transitions->{$TransitionEntityID}->{Condition} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No conditions for transition $TransitionEntityID found!",
);
return;
}
my $ConditionLinking = $Transitions->{$TransitionEntityID}->{ConditionLinking} || '';
# If we don't have a ConditionLinking set it to 'and' by default compatibility with OTRS 3.3.x
if ( !$ConditionLinking ) {
$ConditionLinking = $Transitions->{$TransitionEntityID}->{Condition}->{Type} || 'and';
}
if (
!$Transitions->{$TransitionEntityID}->{Condition}->{ConditionLinking}
&& !$Transitions->{$TransitionEntityID}->{Condition}->{Type}
)
{
$Self->DebugLog(
MessageType => 'Custom',
Message =>
"Transition:'$Transitions->{$TransitionEntityID}->{Name}' No Condition Linking"
. " as Condition->Type or Condition->ConditionLinking was found, using 'and' as"
. " default!",
);
}
# If there is something else than 'and', 'or', 'xor' log defect Transition Config.
if (
$ConditionLinking ne 'and'
&& $ConditionLinking ne 'or'
&& $ConditionLinking ne 'xor'
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Invalid Condition->Type in $TransitionEntityID!",
);
return;
}
my ( $ConditionSuccess, $ConditionFail ) = ( 0, 0 );
CONDITIONNAME:
for my $ConditionName (
sort { $a cmp $b }
keys %{ $Transitions->{$TransitionEntityID}->{Condition} }
)
{
next CONDITIONNAME if $ConditionName eq 'Type' || $ConditionName eq 'ConditionLinking';
# Get the condition.
my $ActualCondition = $Transitions->{$TransitionEntityID}->{Condition}->{$ConditionName};
# Check if we have Fields in our Condition.
if ( !IsHashRefWithData( $ActualCondition->{Fields} ) )
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No Fields in Transition $TransitionEntityID->Condition->$ConditionName"
. " found!",
);
return;
}
# If we don't have a Condition->$ConditionName->Type, set it to 'and' by default.
my $CondType = $ActualCondition->{Type} || 'and';
if ( !$ActualCondition->{Type} ) {
$Self->DebugLog(
MessageType => 'Custom',
Message =>
"Transition:'$Transitions->{$TransitionEntityID}->{Name}' Condition:'$ConditionName'"
. " No Condition Type found, using 'and' as default",
);
}
# If there is something else than 'and', 'or', 'xor' log defect Transition Config.
if ( $CondType ne 'and' && $CondType ne 'or' && $CondType ne 'xor' ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Invalid Condition->$ConditionName->Type in $TransitionEntityID!",
);
return;
}
my ( $FieldSuccess, $FieldFail ) = ( 0, 0 );
FIELDLNAME:
for my $FieldName ( sort keys %{ $ActualCondition->{Fields} } ) {
# If we have just a String transform it into string check condition.
if ( ref $ActualCondition->{Fields}->{$FieldName} eq '' ) {
$ActualCondition->{Fields}->{$FieldName} = {
Type => 'String',
Match => $ActualCondition->{Fields}->{$FieldName},
};
}
# If we have an Array ref in "Fields" we deal with just values
# -> transform it into a { Type => 'Array', Match => [1,2,3,4] } structure
# to unify testing later on.
if ( ref $ActualCondition->{Fields}->{$FieldName} eq 'ARRAY' ) {
$ActualCondition->{Fields}->{$FieldName} = {
Type => 'Array',
Match => $ActualCondition->{Fields}->{$FieldName},
};
}
# If we don't have a Condition->$ConditionName->Fields->Field->Type set it to
# 'String' by default.
my $FieldType = $ActualCondition->{Fields}->{$FieldName}->{Type} || 'String';
if ( !$ActualCondition->{Fields}->{$FieldName}->{Type} ) {
$Self->DebugLog(
MessageType => 'Custom',
Message =>
"Transition:'$Transitions->{$TransitionEntityID}->{Name}'"
. " Condition:'$ConditionName' Field:'$FieldName'"
. " No Field Type found, using 'String' as default",
);
}
# If there is something else than 'String', 'Regexp', 'Hash', 'Array', 'Module'
# log defect Transition Config.
if (
$FieldType ne 'String'
&& $FieldType ne 'Hash'
&& $FieldType ne 'Array'
&& $FieldType ne 'Regexp'
&& $FieldType ne 'Module'
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Invalid Condition->Type in $TransitionEntityID!",
);
return;
}
if ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'String' ) {
# if our Check contains anything else than a string we can't check
# Special Condition: if Match contains '0' we can check.
if (
(
!$ActualCondition->{Fields}->{$FieldName}->{Match}
&& $ActualCondition->{Fields}->{$FieldName}->{Match} ne '0'
)
|| ref $ActualCondition->{Fields}->{$FieldName}->{Match}
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"$TransitionEntityID->Condition->$ConditionName->Fields->$FieldName Match must"
. " be a String if Type is set to String!",
);
return;
}
my $Match;
my $MatchValue;
# Make sure there is data to compare.
if (
defined $Param{Data}->{$FieldName}
&& defined $ActualCondition->{Fields}->{$FieldName}->{Match}
)
{
# Check if field data is a string and compare directly.
if (
ref $Param{Data}->{$FieldName} eq ''
&& $ActualCondition->{Fields}->{$FieldName}->{Match} eq $Param{Data}->{$FieldName}
)
{
$Match = 1;
$MatchValue = $Param{Data}->{$FieldName};
}
# Otherwise check if field data is and array and compare each element until
# one match.
elsif ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) {
ITEM:
for my $Item ( @{ $Param{Data}->{$FieldName} } ) {
if ( $ActualCondition->{Fields}->{$FieldName}->{Match} eq $Item ) {
$Match = 1;
$MatchValue = "Item: [$Item]";
last ITEM;
}
}
}
}
if ($Match) {
$FieldSuccess++;
$Self->DebugLog(
MessageType => 'Match',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'String',
MatchValue => $MatchValue,
MatchCondition => $ActualCondition->{Fields}->{$FieldName}->{Match}
);
# Successful check if we just need one matching Condition to make this
# Transition valid.
if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
ConditionType => $CondType,
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or';
}
else {
$FieldFail++;
my $UnmatchedValue = $Param{Data}->{$FieldName};
if ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) {
$UnmatchedValue = 'Any of [' . join( ', ', @{ $Param{Data}->{$FieldName} } ) . ']';
}
$Self->DebugLog(
MessageType => 'NoMatch',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'String',
MatchValue => $UnmatchedValue,
MatchCondition => $ActualCondition->{Fields}->{$FieldName}->{Match}
);
# Failed check if we have all 'and' conditions.
next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and';
# Try next Condition if all Condition Fields have to be true.
next CONDITIONNAME if $CondType eq 'and';
}
next FIELDLNAME;
}
elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Array' ) {
# 1. go through each Condition->$ConditionName->Fields->$Field->Value (map).
# 2. assign the value to $CheckValue.
# 3. grep through $Data->{$FieldName} to find the "toCheck" value inside the Data->{$FieldName} Array.
# 4. Assign all found Values to @CheckResults.
my $CheckValue;
my @CheckResults = map {
$CheckValue = $_;
grep { $CheckValue eq $_ } @{ $Param{Data}->{$FieldName} }
}
@{ $ActualCondition->{Fields}->{$FieldName}->{Match} };
# If the found amount is the same as the "toCheck" amount we succeeded.
if (
scalar @CheckResults
== scalar @{ $ActualCondition->{Fields}->{$FieldName}->{Match} }
)
{
$FieldSuccess++;
$Self->DebugLog(
MessageType => 'Match',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'Array',
);
# Successful check if we just need one matching Condition to make this Transition valid.
if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
ConditionType => $CondType,
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or';
}
else {
$FieldFail++;
$Self->DebugLog(
MessageType => 'NoMatch',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'Array',
);
# Failed check if we have all 'and' conditions.
next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and';
# Try next Condition if all Condition Fields have to be true.
next CONDITIONNAME if $CondType eq 'and';
}
next FIELDLNAME;
}
elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Hash' ) {
# If our Check doesn't contain a hash.
if ( ref $ActualCondition->{Fields}->{$FieldName}->{Match} ne 'HASH' ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"$TransitionEntityID->Condition->$ConditionName->Fields->$FieldName Match must"
. " be a Hash!",
);
return;
}
# If we have no data or Data isn't a hash, test failed.
if (
!$Param{Data}->{$FieldName}
|| ref $Param{Data}->{$FieldName} ne 'HASH'
)
{
$FieldFail++;
next FIELDLNAME;
}
# Find all Data Hash values that equal to the Condition Match Values.
my @CheckResults = grep {
$Param{Data}->{$FieldName}->{$_} eq
$ActualCondition->{Fields}->{$FieldName}->{Match}->{$_}
} keys %{ $ActualCondition->{Fields}->{$FieldName}->{Match} };
# If the amount of Results equals the amount of Keys in our hash this part matched.
if (
scalar @CheckResults
== scalar keys %{ $ActualCondition->{Fields}->{$FieldName}->{Match} }
)
{
$FieldSuccess++;
$Self->DebugLog(
MessageType => 'Match',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'Hash',
);
# Successful check if we just need one matching Condition to make this Transition valid.
if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
ConditionType => $CondType,
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or';
}
else {
$FieldFail++;
$Self->DebugLog(
MessageType => 'NoMatch',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'Hash',
);
# Failed check if we have all 'and' conditions.
next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and';
# Try next Condition if all Condition Fields have to be true.
next CONDITIONNAME if $CondType eq 'and';
}
next FIELDLNAME;
}
elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Regexp' )
{
# If our Check contains anything else then a string we can't check.
if (
!$ActualCondition->{Fields}->{$FieldName}->{Match}
||
(
ref $ActualCondition->{Fields}->{$FieldName}->{Match} ne 'Regexp'
&& ref $ActualCondition->{Fields}->{$FieldName}->{Match} ne ''
)
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"$TransitionEntityID->Condition->$ConditionName->Fields->$FieldName Match must"
. " be a Regular expression if Type is set to Regexp!",
);
return;
}
# Precompile Regexp if is a string.
if ( ref $ActualCondition->{Fields}->{$FieldName}->{Match} eq '' ) {
my $Match = $ActualCondition->{Fields}->{$FieldName}->{Match};
eval {
$ActualCondition->{Fields}->{$FieldName}->{Match} = qr{$Match};
};
if ($@) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => $@,
);
return;
}
}
my $Match;
my $MatchValue;
# Make sure there is data to compare.
if ( $Param{Data}->{$FieldName} ) {
# Check if field data is a string and compare directly.
if (
ref $Param{Data}->{$FieldName} eq ''
&& $Param{Data}->{$FieldName} =~ $ActualCondition->{Fields}->{$FieldName}->{Match}
)
{
$Match = 1;
$MatchValue = $Param{Data}->{$FieldName};
}
# Otherwise check if field data is and array and compare each element until one match.
elsif ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) {
ITEM:
for my $Item ( @{ $Param{Data}->{$FieldName} } ) {
if ( $Item =~ $ActualCondition->{Fields}->{$FieldName}->{Match} ) {
$Match = 1;
$MatchValue = "Item: [$Item]";
last ITEM;
}
}
}
}
if ($Match) {
$FieldSuccess++;
$Self->DebugLog(
MessageType => 'Match',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'Regexp',
MatchValue => $MatchValue,
MatchCondition => $ActualCondition->{Fields}->{$FieldName}->{Match}
);
# Successful check if we just need one matching Condition to make this Transition valid.
if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
ConditionType => $CondType,
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or';
}
else {
$FieldFail++;
my $UnmatchedValue = $Param{Data}->{$FieldName};
if ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) {
$UnmatchedValue = 'Any of [' . join( ', ', @{ $Param{Data}->{$FieldName} } ) . ']';
}
$Self->DebugLog(
MessageType => 'NoMatch',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'Regexp',
MatchValue => $UnmatchedValue,
MatchCondition => $ActualCondition->{Fields}->{$FieldName}->{Match}
);
# Failed check if we have all 'and' conditions.
next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and';
# Try next Condition if all Condition Fields have to be true.
next CONDITIONNAME if $CondType eq 'and';
}
next FIELDLNAME;
}
elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Module' ) {
# Load Validation Modules.
# Default location for validation modules:
# Kernel/System/ProcessManagement/TransitionValidation/.
if (
!$Kernel::OM->Get('Kernel::System::Main')
->Require( $ActualCondition->{Fields}->{$FieldName}->{Match} )
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Can't load "
. $ActualCondition->{Fields}->{$FieldName}->{Type}
. "Module for Transition->$TransitionEntityID->Condition->$ConditionName->"
. "Fields->$FieldName validation!",
);
return;
}
# Create new ValidateModuleObject.
my $ValidateModuleObject = $ActualCondition->{Fields}->{$FieldName}->{Match}->new();
# Handle "Data" Param to ValidateModule's "Validate" subroutine.
if ( $ValidateModuleObject->Validate( Data => $Param{Data} ) ) {
$FieldSuccess++;
$Self->DebugLog(
MessageType => 'Match',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'Module',
Module => $ActualCondition->{Fields}->{$FieldName}->{Type}
);
# Successful check if we just need one matching Condition to make this Transition valid.
if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
ConditionType => $CondType,
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or';
}
else {
$FieldFail++;
$Self->DebugLog(
MessageType => 'NoMatch',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
FieldName => $FieldName,
MatchType => 'Module',
Module => $ActualCondition->{Fields}->{$FieldName}->{Type}
);
# Failed check if we have all 'and' conditions.
next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and';
# Try next Condition if all Condition Fields have to be true.
next CONDITIONNAME if $CondType eq 'and';
}
next FIELDLNAME;
}
}
# FIELDLNAME End.
if ( $CondType eq 'and' ) {
# if we had no failing check this condition matched.
if ( !$FieldFail ) {
# Successful check if we just need one matching Condition to make this Transition valid.
if ( $ConditionLinking eq 'or' ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
ConditionType => $CondType,
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
$ConditionSuccess++;
}
else {
$ConditionFail++;
# Failed check if we have all 'and' conditions.
next TRANSITIONENTITYID if $ConditionLinking eq 'and';
}
}
elsif ( $CondType eq 'or' )
{
# If we had at least one successful check, this condition matched.
if ( $FieldSuccess > 0 ) {
# Successful check if we just need one matching Condition to make this Transition valid.
if ( $ConditionLinking eq 'or' ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
ConditionType => $CondType,
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
$ConditionSuccess++;
}
else {
$ConditionFail++;
# Failed check if we have all 'and' conditions.
next TRANSITIONENTITYID if $ConditionLinking eq 'and';
}
}
elsif ( $CondType eq 'xor' )
{
# if we had exactly one successful check, this condition matched.
if ( $FieldSuccess == 1 ) {
# Successful check if we just need one matching Condition to make this Transition valid.
if ( $ConditionLinking eq 'or' ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionName => $ConditionName,
ConditionType => $CondType,
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
$ConditionSuccess++;
}
else {
$ConditionFail++;
}
}
}
# CONDITIONNAME End.
if ( $ConditionLinking eq 'and' ) {
# If we had no failing conditions this transition matched.
if ( !$ConditionFail ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
}
elsif ( $ConditionLinking eq 'or' )
{
# If we had at least one successful condition, this transition matched.
if ( $ConditionSuccess > 0 ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
}
elsif ( $ConditionLinking eq 'xor' )
{
# If we had exactly one successful condition, this transition matched.
if ( $ConditionSuccess == 1 ) {
$Self->DebugLog(
MessageType => 'Success',
TransitionName => $Transitions->{$TransitionEntityID}->{Name},
ConditionLinking => $ConditionLinking,
);
return $TransitionEntityID;
}
}
}
# TRANSITIONENTITYID End.
# If no transition matched till here, we failed.
return;
}
sub DebugLog {
my ( $Self, %Param ) = @_;
return 1 if !$Self->{TransitionDebug};
my $Message = "Transition:'$Param{TransitionName}'";
if ( $Param{MessageType} eq 'Match' || $Param{MessageType} eq 'NoMatch' ) {
my $MatchedString = $Param{MessageType} eq 'Match' ? 'Matched' : 'Not matched';
$Message = " Condition:'$Param{ConditionName}' $MatchedString Field:'$Param{FieldName}'";
if ( $Param{MatchType} eq 'Module' ) {
$Message .= " with Transition Validation Module: '$Param{Module}'";
}
else {
$Message .= " as $Param{MatchType}";
}
if ( $Param{MatchValue} && $Param{MatchCondition} ) {
$Message .= " ($Param{MatchValue} matches $Param{MatchCondition})";
}
}
elsif ( $Param{MessageType} eq 'Success' ) {
if ( $Param{ConditionName} && $Param{ConditionType} ) {
$Message = " Condition:'$Param{ConditionName}' Success on Condition Linking:'$Param{ConditionLinking}'"
. " and Condition Type:'$Param{ConditionType}'";
}
else {
$Message = " Success on Condition Linking:'$Param{ConditionLinking}'";
}
}
# for MessageType 'Custom' or any other, use the given message
else {
return if !$Param{Message};
$Message = $Param{Message};
}
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => $Self->{TransitionDebugLogPriority},
Message => $Message,
);
return 1;
}
1;
=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