# -- # 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{]+)>}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). 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