1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::System::ProcessManagement::Transition; 10 11use strict; 12use warnings; 13 14use Kernel::System::VariableCheck qw(:all); 15 16our @ObjectDependencies = ( 17 'Kernel::Config', 18 'Kernel::System::Log', 19 'Kernel::System::Main', 20); 21 22=head1 NAME 23 24Kernel::System::ProcessManagement::Transition - Transition lib 25 26=head1 DESCRIPTION 27 28All Process Management Transition functions. 29 30=head1 PUBLIC INTERFACE 31 32=head2 new() 33 34Don't use the constructor directly, use the ObjectManager instead: 35 36 my $TransitionObject = $Kernel::OM->Get('Kernel::System::ProcessManagement::Transition'); 37 38=cut 39 40sub new { 41 my ( $Type, %Param ) = @_; 42 43 # allocate new hash for object 44 my $Self = {}; 45 bless( $Self, $Type ); 46 47 # get config object 48 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 49 50 # get the debug parameters 51 $Self->{TransitionDebug} = $ConfigObject->Get('ProcessManagement::Transition::Debug::Enabled') || 0; 52 $Self->{TransitionDebugLogPriority} 53 = $ConfigObject->Get('ProcessManagement::Transition::Debug::LogPriority') || 'debug'; 54 55 my $TransitionDebugConfigFilters = $ConfigObject->Get('ProcessManagement::Transition::Debug::Filter') || {}; 56 57 for my $FilterName ( sort keys %{$TransitionDebugConfigFilters} ) { 58 59 my %Filter = %{ $TransitionDebugConfigFilters->{$FilterName} }; 60 61 for my $FilterItem ( sort keys %Filter ) { 62 $Self->{TransitionDebugFilters}->{$FilterItem} = $Filter{$FilterItem}; 63 } 64 } 65 66 return $Self; 67} 68 69=head2 TransitionGet() 70 71 Get Transition info 72 73 my $Transition = $TransitionObject->TransitionGet( 74 TransitionEntityID => 'T1', 75 ); 76 77 Returns: 78 79 $Transition = { 80 Name => 'Transition 1', 81 CreateTime => '08-02-2012 13:37:00', 82 ChangeBy => '2', 83 ChangeTime => '09-02-2012 13:37:00', 84 CreateBy => '3', 85 Condition => { 86 Type => 'and', 87 Cond1 => { 88 Type => 'and', 89 Fields => { 90 DynamicField_Make => [ '2' ], 91 DynamicField_VWModel => { 92 Type => 'String', 93 Match => 'Golf', 94 }, 95 DynamicField_A => { 96 Type => 'Hash', 97 Match => { 98 Value => 1, 99 }, 100 }, 101 DynamicField_B => { 102 Type => 'Regexp', 103 Match => qr{ [\n\r\f] }xms 104 }, 105 DynamicField_C => { 106 Type => 'Module', 107 Match => 108 'Kernel::System::ProcessManagement::TransitionValidation::MyModule', 109 }, 110 Queue => { 111 Type => 'Array', 112 Match => [ 'Raw' ], 113 }, 114 # ... 115 } 116 } 117 # ... 118 }, 119 }; 120 121=cut 122 123sub TransitionGet { 124 my ( $Self, %Param ) = @_; 125 126 for my $Needed (qw(TransitionEntityID)) { 127 if ( !defined $Param{$Needed} ) { 128 $Kernel::OM->Get('Kernel::System::Log')->Log( 129 Priority => 'error', 130 Message => "Need $Needed!", 131 ); 132 return; 133 } 134 } 135 136 my $Transition = $Kernel::OM->Get('Kernel::Config')->Get('Process::Transition'); 137 138 if ( !IsHashRefWithData($Transition) ) { 139 $Kernel::OM->Get('Kernel::System::Log')->Log( 140 Priority => 'error', 141 Message => 'Need Transition config!', 142 ); 143 return; 144 } 145 146 if ( !IsHashRefWithData( $Transition->{ $Param{TransitionEntityID} } ) ) { 147 $Kernel::OM->Get('Kernel::System::Log')->Log( 148 Priority => 'error', 149 Message => "No data for Transition '$Param{TransitionEntityID}' found!", 150 ); 151 return; 152 } 153 154 return $Transition->{ $Param{TransitionEntityID} }; 155} 156 157=head2 TransitionCheck() 158 159 Checks if one or more Transition Conditions are true 160 161 my $TransitionCheck = $TransitionObject->TransitionCheck( 162 TransitionEntityID => 'T1', 163 or 164 TransitionEntityID => ['T1', 'T2', 'T3'], 165 Data => { 166 Queue => 'Raw', 167 DynamicField1 => 'Value', 168 Subject => 'Testsubject', 169 ... 170 }, 171 ); 172 173 If called on a single TransitionEntityID 174 Returns: 175 $Checked = 1; # 0 176 177 If called on an array of TransitionEntityIDs 178 Returns: 179 $Checked = 'T1' # 0 if no Transition was true 180 181=cut 182 183sub TransitionCheck { 184 my ( $Self, %Param ) = @_; 185 186 # Check if we have TransitionEntityID and Data. 187 for my $Needed (qw(TransitionEntityID Data)) { 188 if ( !defined $Param{$Needed} ) { 189 $Kernel::OM->Get('Kernel::System::Log')->Log( 190 Priority => 'error', 191 Message => "Need $Needed!", 192 ); 193 return; 194 } 195 } 196 197 # Check if TransitionEntityID is not empty (either Array or String with length). 198 if ( !length $Param{TransitionEntityID} ) { 199 $Kernel::OM->Get('Kernel::System::Log')->Log( 200 Priority => 'error', 201 Message => "Need TransitionEntityID or TransitionEntityID array!", 202 ); 203 return; 204 } 205 206 # Check if debug filters apply (ticket). 207 if ( $Self->{TransitionDebug} ) { 208 209 DEBUGFILTER: 210 for my $DebugFilter ( sort keys %{ $Self->{TransitionDebugFilters} } ) { 211 next DEBUGFILTER if $DebugFilter eq 'TransitionEntityID'; 212 next DEBUGFILTER if !$Self->{TransitionDebugFilters}->{$DebugFilter}; 213 next DEBUGFILTER if ref $Param{Data} ne 'HASH'; 214 215 if ( $DebugFilter =~ m{<OTRS_TICKET_([^>]+)>}msx ) { 216 my $TicketParam = $1; 217 218 if ( 219 defined $Param{Data}->{$TicketParam} 220 && $Param{Data}->{$TicketParam} 221 ) 222 { 223 if ( ref $Param{Data}->{$TicketParam} eq 'ARRAY' ) { 224 for my $Item ( @{ $Param{Data}->{$TicketParam} } ) { 225 226 # If matches for one item go to next filter (debug keeps active). 227 if ( $Self->{TransitionDebugFilters}->{$DebugFilter} eq $Item ) { 228 next DEBUGFILTER; 229 } 230 } 231 232 # If no matches then deactivate debug. 233 $Self->{TransitionDebug} = 0; 234 last DEBUGFILTER; 235 } 236 237 elsif ( 238 $Self->{TransitionDebugFilters}->{$DebugFilter} ne 239 $Param{Data}->{$TicketParam} 240 ) 241 { 242 $Self->{TransitionDebug} = 0; 243 last DEBUGFILTER; 244 } 245 246 elsif ( !defined $Param{Data}->{$TicketParam} ) { 247 $Self->{TransitionDebug} = 0; 248 last DEBUGFILTER; 249 } 250 } 251 } 252 } 253 } 254 255 # If we got just a string, make $Param{TransitionEntityID} an Array with the string on position 256 # 0 to facilitate handling 257 if ( !IsArrayRefWithData( $Param{TransitionEntityID} ) ) { 258 $Param{TransitionEntityID} = [ $Param{TransitionEntityID} ]; 259 } 260 261 # Check if we have Data to check against transitions conditions. 262 if ( !IsHashRefWithData( $Param{Data} ) ) { 263 $Kernel::OM->Get('Kernel::System::Log')->Log( 264 Priority => 'error', 265 Message => "Data has no values!", 266 ); 267 return; 268 } 269 270 # Get all transitions. 271 my $Transitions = $Kernel::OM->Get('Kernel::Config')->Get('Process::Transition'); 272 273 # Check if there are Transitions. 274 if ( !IsHashRefWithData($Transitions) ) { 275 $Kernel::OM->Get('Kernel::System::Log')->Log( 276 Priority => 'error', 277 Message => 'Need transition config!', 278 ); 279 return; 280 } 281 282 $Self->{TransitionDebugOrig} = $Self->{TransitionDebug}; 283 284 # Loop through all submitted TransitionEntityID's. 285 TRANSITIONENTITYID: 286 for my $TransitionEntityID ( @{ $Param{TransitionEntityID} } ) { 287 288 $Self->{TransitionDebug} = $Self->{TransitionDebugOrig}; 289 290 # Check if debug filters apply (Transition) (only if TransitionDebug is active). 291 if ( 292 $Self->{TransitionDebug} 293 && defined $Self->{TransitionDebugFilters}->{'TransitionEntityID'} 294 && $Self->{TransitionDebugFilters}->{'TransitionEntityID'} ne $TransitionEntityID 295 ) 296 { 297 $Self->{TransitionDebug} = 0; 298 } 299 300 # Check if the submitted TransitionEntityID has a config. 301 if ( !IsHashRefWithData( $Transitions->{$TransitionEntityID} ) ) { 302 $Kernel::OM->Get('Kernel::System::Log')->Log( 303 Priority => 'error', 304 Message => "No config data for transition $TransitionEntityID found!", 305 ); 306 return; 307 } 308 309 # Check if we have TransitionConditions. 310 if ( !IsHashRefWithData( $Transitions->{$TransitionEntityID}->{Condition} ) ) { 311 $Kernel::OM->Get('Kernel::System::Log')->Log( 312 Priority => 'error', 313 Message => "No conditions for transition $TransitionEntityID found!", 314 ); 315 return; 316 } 317 318 my $ConditionLinking = $Transitions->{$TransitionEntityID}->{ConditionLinking} || ''; 319 320 # If we don't have a ConditionLinking set it to 'and' by default compatibility with OTRS 3.3.x 321 if ( !$ConditionLinking ) { 322 $ConditionLinking = $Transitions->{$TransitionEntityID}->{Condition}->{Type} || 'and'; 323 } 324 if ( 325 !$Transitions->{$TransitionEntityID}->{Condition}->{ConditionLinking} 326 && !$Transitions->{$TransitionEntityID}->{Condition}->{Type} 327 ) 328 { 329 330 $Self->DebugLog( 331 MessageType => 'Custom', 332 Message => 333 "Transition:'$Transitions->{$TransitionEntityID}->{Name}' No Condition Linking" 334 . " as Condition->Type or Condition->ConditionLinking was found, using 'and' as" 335 . " default!", 336 ); 337 } 338 339 # If there is something else than 'and', 'or', 'xor' log defect Transition Config. 340 if ( 341 $ConditionLinking ne 'and' 342 && $ConditionLinking ne 'or' 343 && $ConditionLinking ne 'xor' 344 ) 345 { 346 $Kernel::OM->Get('Kernel::System::Log')->Log( 347 Priority => 'error', 348 Message => "Invalid Condition->Type in $TransitionEntityID!", 349 ); 350 return; 351 } 352 my ( $ConditionSuccess, $ConditionFail ) = ( 0, 0 ); 353 354 CONDITIONNAME: 355 for my $ConditionName ( 356 sort { $a cmp $b } 357 keys %{ $Transitions->{$TransitionEntityID}->{Condition} } 358 ) 359 { 360 361 next CONDITIONNAME if $ConditionName eq 'Type' || $ConditionName eq 'ConditionLinking'; 362 363 # Get the condition. 364 my $ActualCondition = $Transitions->{$TransitionEntityID}->{Condition}->{$ConditionName}; 365 366 # Check if we have Fields in our Condition. 367 if ( !IsHashRefWithData( $ActualCondition->{Fields} ) ) 368 { 369 $Kernel::OM->Get('Kernel::System::Log')->Log( 370 Priority => 'error', 371 Message => "No Fields in Transition $TransitionEntityID->Condition->$ConditionName" 372 . " found!", 373 ); 374 return; 375 } 376 377 # If we don't have a Condition->$ConditionName->Type, set it to 'and' by default. 378 my $CondType = $ActualCondition->{Type} || 'and'; 379 if ( !$ActualCondition->{Type} ) { 380 $Self->DebugLog( 381 MessageType => 'Custom', 382 Message => 383 "Transition:'$Transitions->{$TransitionEntityID}->{Name}' Condition:'$ConditionName'" 384 . " No Condition Type found, using 'and' as default", 385 ); 386 } 387 388 # If there is something else than 'and', 'or', 'xor' log defect Transition Config. 389 if ( $CondType ne 'and' && $CondType ne 'or' && $CondType ne 'xor' ) { 390 $Kernel::OM->Get('Kernel::System::Log')->Log( 391 Priority => 'error', 392 Message => "Invalid Condition->$ConditionName->Type in $TransitionEntityID!", 393 ); 394 return; 395 } 396 397 my ( $FieldSuccess, $FieldFail ) = ( 0, 0 ); 398 399 FIELDLNAME: 400 for my $FieldName ( sort keys %{ $ActualCondition->{Fields} } ) { 401 402 # If we have just a String transform it into string check condition. 403 if ( ref $ActualCondition->{Fields}->{$FieldName} eq '' ) { 404 $ActualCondition->{Fields}->{$FieldName} = { 405 Type => 'String', 406 Match => $ActualCondition->{Fields}->{$FieldName}, 407 }; 408 } 409 410 # If we have an Array ref in "Fields" we deal with just values 411 # -> transform it into a { Type => 'Array', Match => [1,2,3,4] } structure 412 # to unify testing later on. 413 if ( ref $ActualCondition->{Fields}->{$FieldName} eq 'ARRAY' ) { 414 $ActualCondition->{Fields}->{$FieldName} = { 415 Type => 'Array', 416 Match => $ActualCondition->{Fields}->{$FieldName}, 417 }; 418 } 419 420 # If we don't have a Condition->$ConditionName->Fields->Field->Type set it to 421 # 'String' by default. 422 my $FieldType = $ActualCondition->{Fields}->{$FieldName}->{Type} || 'String'; 423 if ( !$ActualCondition->{Fields}->{$FieldName}->{Type} ) { 424 $Self->DebugLog( 425 MessageType => 'Custom', 426 Message => 427 "Transition:'$Transitions->{$TransitionEntityID}->{Name}'" 428 . " Condition:'$ConditionName' Field:'$FieldName'" 429 . " No Field Type found, using 'String' as default", 430 ); 431 } 432 433 # If there is something else than 'String', 'Regexp', 'Hash', 'Array', 'Module' 434 # log defect Transition Config. 435 if ( 436 $FieldType ne 'String' 437 && $FieldType ne 'Hash' 438 && $FieldType ne 'Array' 439 && $FieldType ne 'Regexp' 440 && $FieldType ne 'Module' 441 ) 442 { 443 $Kernel::OM->Get('Kernel::System::Log')->Log( 444 Priority => 'error', 445 Message => "Invalid Condition->Type in $TransitionEntityID!", 446 ); 447 return; 448 } 449 450 if ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'String' ) { 451 452 # if our Check contains anything else than a string we can't check 453 # Special Condition: if Match contains '0' we can check. 454 if ( 455 ( 456 !$ActualCondition->{Fields}->{$FieldName}->{Match} 457 && $ActualCondition->{Fields}->{$FieldName}->{Match} ne '0' 458 ) 459 || ref $ActualCondition->{Fields}->{$FieldName}->{Match} 460 ) 461 { 462 $Kernel::OM->Get('Kernel::System::Log')->Log( 463 Priority => 'error', 464 Message => 465 "$TransitionEntityID->Condition->$ConditionName->Fields->$FieldName Match must" 466 . " be a String if Type is set to String!", 467 ); 468 return; 469 } 470 471 my $Match; 472 my $MatchValue; 473 474 # Make sure there is data to compare. 475 if ( 476 defined $Param{Data}->{$FieldName} 477 && defined $ActualCondition->{Fields}->{$FieldName}->{Match} 478 ) 479 { 480 481 # Check if field data is a string and compare directly. 482 if ( 483 ref $Param{Data}->{$FieldName} eq '' 484 && $ActualCondition->{Fields}->{$FieldName}->{Match} eq $Param{Data}->{$FieldName} 485 ) 486 { 487 $Match = 1; 488 $MatchValue = $Param{Data}->{$FieldName}; 489 } 490 491 # Otherwise check if field data is and array and compare each element until 492 # one match. 493 elsif ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) { 494 495 ITEM: 496 for my $Item ( @{ $Param{Data}->{$FieldName} } ) { 497 if ( $ActualCondition->{Fields}->{$FieldName}->{Match} eq $Item ) { 498 $Match = 1; 499 $MatchValue = "Item: [$Item]"; 500 last ITEM; 501 } 502 } 503 } 504 } 505 if ($Match) { 506 $FieldSuccess++; 507 508 $Self->DebugLog( 509 MessageType => 'Match', 510 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 511 ConditionName => $ConditionName, 512 FieldName => $FieldName, 513 MatchType => 'String', 514 MatchValue => $MatchValue, 515 MatchCondition => $ActualCondition->{Fields}->{$FieldName}->{Match} 516 ); 517 518 # Successful check if we just need one matching Condition to make this 519 # Transition valid. 520 if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) { 521 522 $Self->DebugLog( 523 MessageType => 'Success', 524 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 525 ConditionName => $ConditionName, 526 ConditionType => $CondType, 527 ConditionLinking => $ConditionLinking, 528 ); 529 530 return $TransitionEntityID; 531 } 532 533 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 534 } 535 else { 536 $FieldFail++; 537 538 my $UnmatchedValue = $Param{Data}->{$FieldName}; 539 if ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) { 540 $UnmatchedValue = 'Any of [' . join( ', ', @{ $Param{Data}->{$FieldName} } ) . ']'; 541 } 542 543 $Self->DebugLog( 544 MessageType => 'NoMatch', 545 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 546 ConditionName => $ConditionName, 547 FieldName => $FieldName, 548 MatchType => 'String', 549 MatchValue => $UnmatchedValue, 550 MatchCondition => $ActualCondition->{Fields}->{$FieldName}->{Match} 551 ); 552 553 # Failed check if we have all 'and' conditions. 554 next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and'; 555 556 # Try next Condition if all Condition Fields have to be true. 557 next CONDITIONNAME if $CondType eq 'and'; 558 } 559 next FIELDLNAME; 560 } 561 elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Array' ) { 562 563 # 1. go through each Condition->$ConditionName->Fields->$Field->Value (map). 564 # 2. assign the value to $CheckValue. 565 # 3. grep through $Data->{$FieldName} to find the "toCheck" value inside the Data->{$FieldName} Array. 566 # 4. Assign all found Values to @CheckResults. 567 my $CheckValue; 568 my @CheckResults = map { 569 $CheckValue = $_; 570 grep { $CheckValue eq $_ } @{ $Param{Data}->{$FieldName} } 571 } 572 @{ $ActualCondition->{Fields}->{$FieldName}->{Match} }; 573 574 # If the found amount is the same as the "toCheck" amount we succeeded. 575 if ( 576 scalar @CheckResults 577 == scalar @{ $ActualCondition->{Fields}->{$FieldName}->{Match} } 578 ) 579 { 580 $FieldSuccess++; 581 582 $Self->DebugLog( 583 MessageType => 'Match', 584 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 585 ConditionName => $ConditionName, 586 FieldName => $FieldName, 587 MatchType => 'Array', 588 ); 589 590 # Successful check if we just need one matching Condition to make this Transition valid. 591 if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) { 592 593 $Self->DebugLog( 594 MessageType => 'Success', 595 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 596 ConditionName => $ConditionName, 597 ConditionType => $CondType, 598 ConditionLinking => $ConditionLinking, 599 600 ); 601 602 return $TransitionEntityID; 603 } 604 605 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 606 } 607 else { 608 $FieldFail++; 609 610 $Self->DebugLog( 611 MessageType => 'NoMatch', 612 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 613 ConditionName => $ConditionName, 614 FieldName => $FieldName, 615 MatchType => 'Array', 616 ); 617 618 # Failed check if we have all 'and' conditions. 619 next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and'; 620 621 # Try next Condition if all Condition Fields have to be true. 622 next CONDITIONNAME if $CondType eq 'and'; 623 } 624 next FIELDLNAME; 625 } 626 elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Hash' ) { 627 628 # If our Check doesn't contain a hash. 629 if ( ref $ActualCondition->{Fields}->{$FieldName}->{Match} ne 'HASH' ) { 630 $Kernel::OM->Get('Kernel::System::Log')->Log( 631 Priority => 'error', 632 Message => 633 "$TransitionEntityID->Condition->$ConditionName->Fields->$FieldName Match must" 634 . " be a Hash!", 635 ); 636 return; 637 } 638 639 # If we have no data or Data isn't a hash, test failed. 640 if ( 641 !$Param{Data}->{$FieldName} 642 || ref $Param{Data}->{$FieldName} ne 'HASH' 643 ) 644 { 645 $FieldFail++; 646 next FIELDLNAME; 647 } 648 649 # Find all Data Hash values that equal to the Condition Match Values. 650 my @CheckResults = grep { 651 $Param{Data}->{$FieldName}->{$_} eq 652 $ActualCondition->{Fields}->{$FieldName}->{Match}->{$_} 653 } keys %{ $ActualCondition->{Fields}->{$FieldName}->{Match} }; 654 655 # If the amount of Results equals the amount of Keys in our hash this part matched. 656 if ( 657 scalar @CheckResults 658 == scalar keys %{ $ActualCondition->{Fields}->{$FieldName}->{Match} } 659 ) 660 { 661 662 $FieldSuccess++; 663 664 $Self->DebugLog( 665 MessageType => 'Match', 666 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 667 ConditionName => $ConditionName, 668 FieldName => $FieldName, 669 MatchType => 'Hash', 670 ); 671 672 # Successful check if we just need one matching Condition to make this Transition valid. 673 if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) { 674 675 $Self->DebugLog( 676 MessageType => 'Success', 677 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 678 ConditionName => $ConditionName, 679 ConditionType => $CondType, 680 ConditionLinking => $ConditionLinking, 681 ); 682 683 return $TransitionEntityID; 684 } 685 686 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 687 688 } 689 else { 690 $FieldFail++; 691 692 $Self->DebugLog( 693 MessageType => 'NoMatch', 694 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 695 ConditionName => $ConditionName, 696 FieldName => $FieldName, 697 MatchType => 'Hash', 698 ); 699 700 # Failed check if we have all 'and' conditions. 701 next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and'; 702 703 # Try next Condition if all Condition Fields have to be true. 704 next CONDITIONNAME if $CondType eq 'and'; 705 } 706 next FIELDLNAME; 707 } 708 elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Regexp' ) 709 { 710 711 # If our Check contains anything else then a string we can't check. 712 if ( 713 !$ActualCondition->{Fields}->{$FieldName}->{Match} 714 || 715 ( 716 ref $ActualCondition->{Fields}->{$FieldName}->{Match} ne 'Regexp' 717 && ref $ActualCondition->{Fields}->{$FieldName}->{Match} ne '' 718 ) 719 ) 720 { 721 $Kernel::OM->Get('Kernel::System::Log')->Log( 722 Priority => 'error', 723 Message => 724 "$TransitionEntityID->Condition->$ConditionName->Fields->$FieldName Match must" 725 . " be a Regular expression if Type is set to Regexp!", 726 ); 727 return; 728 } 729 730 # Precompile Regexp if is a string. 731 if ( ref $ActualCondition->{Fields}->{$FieldName}->{Match} eq '' ) { 732 my $Match = $ActualCondition->{Fields}->{$FieldName}->{Match}; 733 734 eval { 735 $ActualCondition->{Fields}->{$FieldName}->{Match} = qr{$Match}; 736 }; 737 if ($@) { 738 $Kernel::OM->Get('Kernel::System::Log')->Log( 739 Priority => 'error', 740 Message => $@, 741 ); 742 return; 743 } 744 } 745 746 my $Match; 747 my $MatchValue; 748 749 # Make sure there is data to compare. 750 if ( $Param{Data}->{$FieldName} ) { 751 752 # Check if field data is a string and compare directly. 753 if ( 754 ref $Param{Data}->{$FieldName} eq '' 755 && $Param{Data}->{$FieldName} =~ $ActualCondition->{Fields}->{$FieldName}->{Match} 756 ) 757 { 758 $Match = 1; 759 $MatchValue = $Param{Data}->{$FieldName}; 760 } 761 762 # Otherwise check if field data is and array and compare each element until one match. 763 elsif ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) { 764 765 ITEM: 766 for my $Item ( @{ $Param{Data}->{$FieldName} } ) { 767 if ( $Item =~ $ActualCondition->{Fields}->{$FieldName}->{Match} ) { 768 $Match = 1; 769 $MatchValue = "Item: [$Item]"; 770 last ITEM; 771 } 772 } 773 } 774 } 775 776 if ($Match) { 777 $FieldSuccess++; 778 779 $Self->DebugLog( 780 MessageType => 'Match', 781 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 782 ConditionName => $ConditionName, 783 FieldName => $FieldName, 784 MatchType => 'Regexp', 785 MatchValue => $MatchValue, 786 MatchCondition => $ActualCondition->{Fields}->{$FieldName}->{Match} 787 ); 788 789 # Successful check if we just need one matching Condition to make this Transition valid. 790 if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) { 791 792 $Self->DebugLog( 793 MessageType => 'Success', 794 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 795 ConditionName => $ConditionName, 796 ConditionType => $CondType, 797 ConditionLinking => $ConditionLinking, 798 ); 799 800 return $TransitionEntityID; 801 } 802 803 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 804 } 805 else { 806 $FieldFail++; 807 808 my $UnmatchedValue = $Param{Data}->{$FieldName}; 809 if ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) { 810 $UnmatchedValue = 'Any of [' . join( ', ', @{ $Param{Data}->{$FieldName} } ) . ']'; 811 } 812 813 $Self->DebugLog( 814 MessageType => 'NoMatch', 815 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 816 ConditionName => $ConditionName, 817 FieldName => $FieldName, 818 MatchType => 'Regexp', 819 MatchValue => $UnmatchedValue, 820 MatchCondition => $ActualCondition->{Fields}->{$FieldName}->{Match} 821 ); 822 823 # Failed check if we have all 'and' conditions. 824 next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and'; 825 826 # Try next Condition if all Condition Fields have to be true. 827 next CONDITIONNAME if $CondType eq 'and'; 828 } 829 next FIELDLNAME; 830 } 831 elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Module' ) { 832 833 # Load Validation Modules. 834 # Default location for validation modules: 835 # Kernel/System/ProcessManagement/TransitionValidation/. 836 if ( 837 !$Kernel::OM->Get('Kernel::System::Main') 838 ->Require( $ActualCondition->{Fields}->{$FieldName}->{Match} ) 839 ) 840 { 841 $Kernel::OM->Get('Kernel::System::Log')->Log( 842 Priority => 'error', 843 Message => "Can't load " 844 . $ActualCondition->{Fields}->{$FieldName}->{Type} 845 . "Module for Transition->$TransitionEntityID->Condition->$ConditionName->" 846 . "Fields->$FieldName validation!", 847 ); 848 return; 849 } 850 851 # Create new ValidateModuleObject. 852 my $ValidateModuleObject = $ActualCondition->{Fields}->{$FieldName}->{Match}->new(); 853 854 # Handle "Data" Param to ValidateModule's "Validate" subroutine. 855 if ( $ValidateModuleObject->Validate( Data => $Param{Data} ) ) { 856 $FieldSuccess++; 857 858 $Self->DebugLog( 859 MessageType => 'Match', 860 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 861 ConditionName => $ConditionName, 862 FieldName => $FieldName, 863 MatchType => 'Module', 864 Module => $ActualCondition->{Fields}->{$FieldName}->{Type} 865 ); 866 867 # Successful check if we just need one matching Condition to make this Transition valid. 868 if ( $ConditionLinking eq 'or' && $CondType eq 'or' ) { 869 870 $Self->DebugLog( 871 MessageType => 'Success', 872 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 873 ConditionName => $ConditionName, 874 ConditionType => $CondType, 875 ConditionLinking => $ConditionLinking, 876 ); 877 878 return $TransitionEntityID; 879 } 880 881 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 882 } 883 else { 884 $FieldFail++; 885 886 $Self->DebugLog( 887 MessageType => 'NoMatch', 888 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 889 ConditionName => $ConditionName, 890 FieldName => $FieldName, 891 MatchType => 'Module', 892 Module => $ActualCondition->{Fields}->{$FieldName}->{Type} 893 ); 894 895 # Failed check if we have all 'and' conditions. 896 next TRANSITIONENTITYID if $ConditionLinking eq 'and' && $CondType eq 'and'; 897 898 # Try next Condition if all Condition Fields have to be true. 899 next CONDITIONNAME if $CondType eq 'and'; 900 } 901 next FIELDLNAME; 902 } 903 } 904 905 # FIELDLNAME End. 906 if ( $CondType eq 'and' ) { 907 908 # if we had no failing check this condition matched. 909 if ( !$FieldFail ) { 910 911 # Successful check if we just need one matching Condition to make this Transition valid. 912 if ( $ConditionLinking eq 'or' ) { 913 914 $Self->DebugLog( 915 MessageType => 'Success', 916 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 917 ConditionName => $ConditionName, 918 ConditionType => $CondType, 919 ConditionLinking => $ConditionLinking, 920 ); 921 922 return $TransitionEntityID; 923 } 924 $ConditionSuccess++; 925 } 926 else { 927 $ConditionFail++; 928 929 # Failed check if we have all 'and' conditions. 930 next TRANSITIONENTITYID if $ConditionLinking eq 'and'; 931 } 932 } 933 elsif ( $CondType eq 'or' ) 934 { 935 936 # If we had at least one successful check, this condition matched. 937 if ( $FieldSuccess > 0 ) { 938 939 # Successful check if we just need one matching Condition to make this Transition valid. 940 if ( $ConditionLinking eq 'or' ) { 941 942 $Self->DebugLog( 943 MessageType => 'Success', 944 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 945 ConditionName => $ConditionName, 946 ConditionType => $CondType, 947 ConditionLinking => $ConditionLinking, 948 ); 949 950 return $TransitionEntityID; 951 } 952 $ConditionSuccess++; 953 } 954 else { 955 $ConditionFail++; 956 957 # Failed check if we have all 'and' conditions. 958 next TRANSITIONENTITYID if $ConditionLinking eq 'and'; 959 } 960 } 961 elsif ( $CondType eq 'xor' ) 962 { 963 964 # if we had exactly one successful check, this condition matched. 965 if ( $FieldSuccess == 1 ) { 966 967 # Successful check if we just need one matching Condition to make this Transition valid. 968 if ( $ConditionLinking eq 'or' ) { 969 970 $Self->DebugLog( 971 MessageType => 'Success', 972 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 973 ConditionName => $ConditionName, 974 ConditionType => $CondType, 975 ConditionLinking => $ConditionLinking, 976 ); 977 978 return $TransitionEntityID; 979 } 980 $ConditionSuccess++; 981 } 982 else { 983 $ConditionFail++; 984 } 985 } 986 } 987 988 # CONDITIONNAME End. 989 if ( $ConditionLinking eq 'and' ) { 990 991 # If we had no failing conditions this transition matched. 992 if ( !$ConditionFail ) { 993 994 $Self->DebugLog( 995 MessageType => 'Success', 996 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 997 ConditionLinking => $ConditionLinking, 998 ); 999 1000 return $TransitionEntityID; 1001 } 1002 } 1003 elsif ( $ConditionLinking eq 'or' ) 1004 { 1005 1006 # If we had at least one successful condition, this transition matched. 1007 if ( $ConditionSuccess > 0 ) { 1008 1009 $Self->DebugLog( 1010 MessageType => 'Success', 1011 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 1012 ConditionLinking => $ConditionLinking, 1013 ); 1014 1015 return $TransitionEntityID; 1016 } 1017 } 1018 elsif ( $ConditionLinking eq 'xor' ) 1019 { 1020 1021 # If we had exactly one successful condition, this transition matched. 1022 if ( $ConditionSuccess == 1 ) { 1023 1024 $Self->DebugLog( 1025 MessageType => 'Success', 1026 TransitionName => $Transitions->{$TransitionEntityID}->{Name}, 1027 ConditionLinking => $ConditionLinking, 1028 ); 1029 1030 return $TransitionEntityID; 1031 } 1032 } 1033 } 1034 1035 # TRANSITIONENTITYID End. 1036 # If no transition matched till here, we failed. 1037 return; 1038 1039} 1040 1041sub DebugLog { 1042 my ( $Self, %Param ) = @_; 1043 1044 return 1 if !$Self->{TransitionDebug}; 1045 1046 my $Message = "Transition:'$Param{TransitionName}'"; 1047 1048 if ( $Param{MessageType} eq 'Match' || $Param{MessageType} eq 'NoMatch' ) { 1049 1050 my $MatchedString = $Param{MessageType} eq 'Match' ? 'Matched' : 'Not matched'; 1051 1052 $Message = " Condition:'$Param{ConditionName}' $MatchedString Field:'$Param{FieldName}'"; 1053 1054 if ( $Param{MatchType} eq 'Module' ) { 1055 $Message .= " with Transition Validation Module: '$Param{Module}'"; 1056 } 1057 else { 1058 $Message .= " as $Param{MatchType}"; 1059 } 1060 1061 if ( $Param{MatchValue} && $Param{MatchCondition} ) { 1062 $Message .= " ($Param{MatchValue} matches $Param{MatchCondition})"; 1063 } 1064 } 1065 elsif ( $Param{MessageType} eq 'Success' ) { 1066 1067 if ( $Param{ConditionName} && $Param{ConditionType} ) { 1068 $Message = " Condition:'$Param{ConditionName}' Success on Condition Linking:'$Param{ConditionLinking}'" 1069 . " and Condition Type:'$Param{ConditionType}'"; 1070 } 1071 else { 1072 $Message = " Success on Condition Linking:'$Param{ConditionLinking}'"; 1073 } 1074 } 1075 1076 # for MessageType 'Custom' or any other, use the given message 1077 else { 1078 return if !$Param{Message}; 1079 $Message = $Param{Message}; 1080 } 1081 1082 $Kernel::OM->Get('Kernel::System::Log')->Log( 1083 Priority => $Self->{TransitionDebugLogPriority}, 1084 Message => $Message, 1085 ); 1086 1087 return 1; 1088} 1089 10901; 1091 1092=head1 TERMS AND CONDITIONS 1093 1094This software is part of the OTRS project (L<https://otrs.org/>). 1095 1096This software comes with ABSOLUTELY NO WARRANTY. For details, see 1097the enclosed file COPYING for license information (GPL). If you 1098did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 1099 1100=cut 1101