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::GenericInterface::Event::Handler; 10 11use strict; 12use warnings; 13 14use Kernel::System::VariableCheck qw(:all); 15 16use Storable; 17 18our @ObjectDependencies = ( 19 'Kernel::GenericInterface::Requester', 20 'Kernel::System::Scheduler', 21 'Kernel::System::GenericInterface::Webservice', 22 'Kernel::System::Log', 23 'Kernel::System::Event', 24 'Kernel::System::Main', 25 'Kernel::Config', 26 'Kernel::System::Daemon::SchedulerDB', 27 'Kernel::System::DateTime', 28); 29 30=head1 NAME 31 32Kernel::GenericInterface::Event::Handler - GenericInterface event handler 33 34=head1 DESCRIPTION 35 36This event handler intercepts all system events and fires connected GenericInterface 37invokers. 38 39=cut 40 41sub new { 42 my ( $Type, %Param ) = @_; 43 44 # Allocate new hash for object. 45 my $Self = {}; 46 bless( $Self, $Type ); 47 48 return $Self; 49} 50 51sub Run { 52 my ( $Self, %Param ) = @_; 53 54 my $LogObject = $Kernel::OM->Get('Kernel::System::Log'); 55 for my $Needed (qw(Data Event Config)) { 56 if ( !$Param{$Needed} ) { 57 $LogObject->Log( 58 Priority => 'error', 59 Message => "Need $Needed!" 60 ); 61 return; 62 } 63 } 64 65 my $WebserviceObject = $Kernel::OM->Get('Kernel::System::GenericInterface::Webservice'); 66 my $SchedulerObject = $Kernel::OM->Get('Kernel::System::Scheduler'); 67 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 68 my $RequesterObject = $Kernel::OM->Get('Kernel::GenericInterface::Requester'); 69 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 70 71 my %WebserviceList = %{ $WebserviceObject->WebserviceList( Valid => 1 ) }; 72 my %RegisteredEvents = $Kernel::OM->Get('Kernel::System::Event')->EventList(); 73 74 # Loop over web services. 75 WEBSERVICEID: 76 for my $WebserviceID ( sort keys %WebserviceList ) { 77 78 my $WebserviceData = $WebserviceObject->WebserviceGet( 79 ID => $WebserviceID, 80 ); 81 82 next WEBSERVICEID if !IsHashRefWithData( $WebserviceData->{Config} ); 83 next WEBSERVICEID if !IsHashRefWithData( $WebserviceData->{Config}->{Requester} ); 84 next WEBSERVICEID if !IsHashRefWithData( $WebserviceData->{Config}->{Requester}->{Invoker} ); 85 86 # Check invokers of the web service, to see if some might be connected to this event. 87 INVOKER: 88 for my $Invoker ( sort keys %{ $WebserviceData->{Config}->{Requester}->{Invoker} } ) { 89 90 my $InvokerConfig = $WebserviceData->{Config}->{Requester}->{Invoker}->{$Invoker}; 91 92 next INVOKER if ref $InvokerConfig->{Events} ne 'ARRAY'; 93 94 INVOKEREVENT: 95 for my $InvokerEvent ( @{ $InvokerConfig->{Events} } ) { 96 97 # Check if the invoker is connected to this event. 98 next INVOKEREVENT if !IsHashRefWithData($InvokerEvent); 99 next INVOKEREVENT if !IsStringWithData( $InvokerEvent->{Event} ); 100 next INVOKEREVENT if $InvokerEvent->{Event} ne $Param{Event}; 101 102 # Prepare event type. 103 my $EventType; 104 105 # Set the event type (event object like Article or Ticket) and event condition 106 EVENTTYPE: 107 for my $Type ( sort keys %RegisteredEvents ) { 108 my $EventFound = grep { $_ eq $InvokerEvent->{Event} } @{ $RegisteredEvents{$Type} || [] }; 109 110 next EVENTTYPE if !$EventFound; 111 112 $EventType = $Type; 113 last EVENTTYPE; 114 } 115 116 if ( 117 $EventType 118 && IsHashRefWithData( $InvokerEvent->{Condition} ) 119 && IsHashRefWithData( $InvokerEvent->{Condition}->{Condition} ) 120 ) 121 { 122 123 my $BackendObject = $Self->{EventTypeBackendObject}->{$EventType}; 124 125 if ( !$BackendObject ) { 126 127 my $ObjectClass = "Kernel::GenericInterface::Event::ObjectType::$EventType"; 128 129 my $Loaded = $MainObject->Require( 130 $ObjectClass, 131 ); 132 133 if ( !$Loaded ) { 134 $LogObject->Log( 135 Priority => 'error', 136 Message => 137 "Could not load $ObjectClass, skipping condition checks for event $InvokerEvent->{Event}!", 138 ); 139 next INVOKEREVENT; 140 } 141 142 $BackendObject = $Kernel::OM->Get($ObjectClass); 143 144 $Self->{EventTypeBackendObject}->{$EventType} = $BackendObject; 145 } 146 147 # Get object data 148 my %EventData = $BackendObject->DataGet( 149 Data => $Param{Data}, 150 ); 151 152 if ( IsHashRefWithData( \%EventData ) ) { 153 my %ObjectData; 154 155 $Self->_SerializeConfig( 156 Data => \%EventData, 157 SHash => \%ObjectData, 158 ); 159 160 # Check if the event condition matches. 161 my $ConditionCheckResult = $Self->_ConditionCheck( 162 %{ $InvokerEvent->{Condition} }, 163 Data => \%ObjectData, 164 ); 165 166 next INVOKEREVENT if !$ConditionCheckResult; 167 } 168 } 169 170 # create scheduler task for asynchronous tasks 171 if ( $InvokerEvent->{Asynchronous} ) { 172 173 my $Success = $SchedulerObject->TaskAdd( 174 Type => 'GenericInterface', 175 Name => 'Invoker-' . $Invoker, 176 Attempts => 10, 177 Data => { 178 WebserviceID => $WebserviceID, 179 Invoker => $Invoker, 180 Data => $Param{Data}, 181 }, 182 ); 183 if ( !$Success ) { 184 $LogObject->Log( 185 Priority => 'error', 186 Message => 'Could not schedule task for Invoker-' . $Invoker, 187 ); 188 } 189 190 next INVOKEREVENT; 191 192 } 193 194 # execute synchronous tasks directly 195 my $Result = $RequesterObject->Run( 196 WebserviceID => $WebserviceID, 197 Invoker => $Invoker, 198 Data => Storable::dclone( $Param{Data} ), 199 ); 200 next INVOKEREVENT if $Result->{Success}; 201 202 # check if rescheduling is requested on errors 203 next INVOKEREVENT if !IsHashRefWithData( $Result->{Data} ); 204 next INVOKEREVENT if !$Result->{Data}->{ReSchedule}; 205 206 # Use the execution time from the return data 207 my $ExecutionTime = $Result->{Data}->{ExecutionTime}; 208 my $ExecutionDateTime; 209 210 # Check if execution time is valid. 211 if ( IsStringWithData($ExecutionTime) ) { 212 213 $ExecutionDateTime = $Kernel::OM->Create( 214 'Kernel::System::DateTime', 215 ObjectParams => { 216 String => $ExecutionTime, 217 }, 218 ); 219 if ( !$ExecutionDateTime ) { 220 my $WebServiceName = $WebserviceData->{Name} // 'N/A'; 221 $LogObject->Log( 222 Priority => 'error', 223 Message => 224 "WebService $WebServiceName, Invoker $Invoker returned invalid execution time $ExecutionTime. Falling back to default!", 225 ); 226 } 227 } 228 229 # Set default execution time. 230 if ( !$ExecutionTime || !$ExecutionDateTime ) { 231 232 # Get default time difference from config. 233 my $FutureTaskTimeDiff 234 = int( $ConfigObject->Get('Daemon::SchedulerGenericInterfaceTaskManager::FutureTaskTimeDiff') ) 235 || 300; 236 237 $ExecutionDateTime = $Kernel::OM->Create('Kernel::System::DateTime'); 238 $ExecutionDateTime->Add( Seconds => $FutureTaskTimeDiff ); 239 } 240 241 # Create a new task that will be executed in the future. 242 my $Success = $SchedulerObject->TaskAdd( 243 ExecutionTime => $ExecutionDateTime->ToString(), 244 Type => 'GenericInterface', 245 Name => 'Invoker-' . $Invoker, 246 Attempts => 10, 247 Data => { 248 Data => $Param{Data}, 249 PastExecutionData => $Result->{Data}->{PastExecutionData}, 250 WebserviceID => $WebserviceID, 251 Invoker => $Invoker, 252 }, 253 ); 254 if ( !$Success ) { 255 $LogObject->Log( 256 Priority => 'error', 257 Message => 'Could not re-schedule a task in future for Invoker ' . $Invoker, 258 ); 259 } 260 } 261 } 262 } 263 264 return 1; 265} 266 267=head2 _SerializeConfig() 268 269 returns a serialized hash/array of a given hash/array 270 271 my $ConditionCheck = $Self->_SerializeConfig( 272 Data => \%OldHash, 273 SHash => \%NewHash, 274 ); 275 276 Modifies NewHash (SHash): 277 278 my %OldHash = ( 279 Config => { 280 A => 1, 281 B => 2, 282 C => 3, 283 }, 284 Config2 => 1 285 ); 286 287 my %NewHash = ( 288 Config_A => 1, 289 Config_B => 1, 290 Config_C => 1, 291 Config2 => 1, 292 ); 293 294=cut 295 296sub _SerializeConfig { 297 my ( $Self, %Param ) = @_; 298 299 for my $Needed (qw(Data SHash)) { 300 if ( !$Param{$Needed} ) { 301 print "Got no $Needed!\n"; 302 return; 303 } 304 } 305 306 my @ConfigContainer; 307 my $DataType = 'Hash'; 308 309 if ( IsHashRefWithData( $Param{Data} ) ) { 310 @ConfigContainer = sort keys %{ $Param{Data} }; 311 } 312 else { 313 @ConfigContainer = @{ $Param{Data} }; 314 $DataType = 'Array'; 315 } 316 317 # Prepare prefix. 318 my $Prefix = $Param{Prefix} || ''; 319 320 my $ArrayCount = 0; 321 322 CONFIGITEM: 323 for my $ConfigItem (@ConfigContainer) { 324 325 next CONFIGITEM if !$ConfigItem; 326 327 # Check if param data is a hash or an array ref. 328 if ( $DataType eq 'Hash' ) { 329 330 # We got a hash ref. 331 if ( 332 IsHashRefWithData( $Param{Data}->{$ConfigItem} ) 333 || IsArrayRefWithData( $Param{Data}->{$ConfigItem} ) 334 ) 335 { 336 $Self->_SerializeConfig( 337 Data => $Param{Data}->{$ConfigItem}, 338 SHash => $Param{SHash}, 339 Prefix => $Prefix . $ConfigItem . '_', 340 ); 341 } 342 else { 343 344 $Prefix = $Prefix . $ConfigItem; 345 $Param{SHash}->{$Prefix} = $Param{Data}->{$ConfigItem}; 346 $Prefix = $Param{Prefix} || ''; 347 } 348 } 349 350 # We got an array ref 351 else { 352 353 if ( IsHashRefWithData($ConfigItem) || IsArrayRefWithData($ConfigItem) ) { 354 355 $Self->_SerializeConfig( 356 Data => $ConfigItem, 357 SHash => $Param{SHash}, 358 Prefix => $Prefix . $ConfigItem . '_', 359 ); 360 } 361 else { 362 363 $Prefix = $Prefix . $ArrayCount; 364 $Param{SHash}->{$Prefix} = $ConfigItem; 365 $Prefix = $Param{Prefix} || ''; 366 } 367 368 $ArrayCount++; 369 } 370 } 371 372 return 1; 373} 374 375=head2 _ConditionCheck() 376 377 Checks if one or more conditions are true 378 379 my $ConditionCheck = $Self->_ConditionCheck( 380 ConditionLinking => 'and', 381 Condition => { 382 1 => { 383 Type => 'and', 384 Fields => { 385 DynamicField_Make => [ '2' ], 386 DynamicField_VWModel => { 387 Type => 'String', 388 Match => 'Golf', 389 }, 390 DynamicField_A => { 391 Type => 'Hash', 392 Match => { 393 Value => 1, 394 }, 395 }, 396 DynamicField_B => { 397 Type => 'Regexp', 398 Match => qr{ [\n\r\f] }xms 399 }, 400 DynamicField_C => { 401 Type => 'Module', 402 Match => 403 'Kernel::GenericInterface::Event::Validation::MyModule', 404 }, 405 Queue => { 406 Type => 'Array', 407 Match => [ 'Raw' ], 408 }, 409 # ... 410 }, 411 }, 412 # ... 413 }, 414 Data => { 415 Queue => 'Raw', 416 DynamicField1 => 'Value', 417 Subject => 'Testsubject', 418 # ... 419 }, 420 ); 421 422 Returns: 423 424 $CheckResult = 1; # 1 = process with Scheduler or Requester 425 # 0 = stop processing 426 427=cut 428 429sub _ConditionCheck { 430 my ( $Self, %Param ) = @_; 431 432 my $LogObject = $Kernel::OM->Get('Kernel::System::Log'); 433 for my $Needed (qw(Condition Data)) { 434 if ( !defined $Param{$Needed} ) { 435 $LogObject->Log( 436 Priority => 'error', 437 Message => "Need $Needed!", 438 ); 439 440 return; 441 } 442 } 443 444 # Check if we have Data to check against Condition. 445 if ( !IsHashRefWithData( $Param{Data} ) ) { 446 $LogObject->Log( 447 Priority => 'error', 448 Message => "Data has no values!", 449 ); 450 451 return; 452 } 453 454 # Check if we have Condition to check against Data. 455 if ( !IsHashRefWithData( $Param{Condition} ) ) { 456 $LogObject->Log( 457 Priority => 'error', 458 Message => "Condition has no values!", 459 ); 460 461 return; 462 } 463 464 my $ConditionLinking = $Param{ConditionLinking} || 'and'; 465 466 # If there is something else than 'and', 'or', 'xor' log defect condition configuration 467 if ( 468 $ConditionLinking ne 'and' 469 && $ConditionLinking ne 'or' 470 && $ConditionLinking ne 'xor' 471 ) 472 { 473 $LogObject->Log( 474 Priority => 'error', 475 Message => "Invalid ConditionLinking!", 476 ); 477 return; 478 } 479 my ( $ConditionSuccess, $ConditionFail ) = ( 0, 0 ); 480 481 # Loop through all submitted conditions 482 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 483 CONDITIONNAME: 484 for my $ConditionName ( sort { $a cmp $b } keys %{ $Param{Condition} } ) { 485 486 next CONDITIONNAME if $ConditionName eq 'ConditionLinking'; 487 488 # Get the condition data. 489 my $ActualCondition = $Param{Condition}->{$ConditionName}; 490 491 # Check if we have Fields in our Condition 492 if ( !IsHashRefWithData( $ActualCondition->{Fields} ) ) 493 { 494 $LogObject->Log( 495 Priority => 'error', 496 Message => "No Fields in Condition->$ConditionName found!", 497 ); 498 return; 499 } 500 501 # If we don't have a Condition->$Condition->Type, set it to 'and' by default 502 my $CondType = $ActualCondition->{Type} || 'and'; 503 504 # If there is something else than 'and', 'or', 'xor' log defect condition configuration 505 if ( $CondType ne 'and' && $CondType ne 'or' && $CondType ne 'xor' ) { 506 $LogObject->Log( 507 Priority => 'error', 508 Message => "Invalid Condition->$ConditionName->Type!", 509 ); 510 return; 511 } 512 513 my ( $FieldSuccess, $FieldFail ) = ( 0, 0 ); 514 515 FIELDLNAME: 516 for my $FieldName ( sort keys %{ $ActualCondition->{Fields} } ) { 517 518 # If we have just a String transform it into string check condition. 519 if ( ref $ActualCondition->{Fields}->{$FieldName} eq '' ) { 520 $ActualCondition->{Fields}->{$FieldName} = { 521 Type => 'String', 522 Match => $ActualCondition->{Fields}->{$FieldName}, 523 }; 524 } 525 526 # If we have an Array ref in "Fields" we deal with just values 527 # -> transform it into a { Type => 'Array', Match => [1,2,3,4] } structure 528 # to unify testing later on. 529 if ( ref $ActualCondition->{Fields}->{$FieldName} eq 'ARRAY' ) { 530 $ActualCondition->{Fields}->{$FieldName} = { 531 Type => 'Array', 532 Match => $ActualCondition->{Fields}->{$FieldName}, 533 }; 534 } 535 536 # If we don't have a Condition->$ConditionName->Fields->Field->Type 537 # set it to 'String' by default. 538 my $FieldType = $ActualCondition->{Fields}->{$FieldName}->{Type} || 'String'; 539 540 # If there is something else than 'String', 'Regexp', 'Hash', 'Array', 'Module' log 541 # defect config. 542 if ( 543 $FieldType ne 'String' 544 && $FieldType ne 'Hash' 545 && $FieldType ne 'Array' 546 && $FieldType ne 'Regexp' 547 && $FieldType ne 'Module' 548 ) 549 { 550 $LogObject->Log( 551 Priority => 'error', 552 Message => "Invalid Condition->Type!", 553 ); 554 return; 555 } 556 557 if ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'String' ) { 558 559 # If our Check contains anything else than a string we can't check, 560 # Special Condition: if Match contains '0' we can check 561 if ( 562 ( 563 !$ActualCondition->{Fields}->{$FieldName}->{Match} 564 && $ActualCondition->{Fields}->{$FieldName}->{Match} ne '0' 565 ) 566 || ref $ActualCondition->{Fields}->{$FieldName}->{Match} 567 ) 568 { 569 $LogObject->Log( 570 Priority => 'error', 571 Message => 572 "Condition->$ConditionName->Fields->$FieldName Match must" 573 . " be a String if Type is set to String!", 574 ); 575 return; 576 } 577 578 # Make sure the data string is here and it isn't a ref (array or whatsoever) 579 # then compare it to our Condition configuration. 580 if ( 581 defined $Param{Data}->{$FieldName} 582 && defined $ActualCondition->{Fields}->{$FieldName}->{Match} 583 && ( $Param{Data}->{$FieldName} || $Param{Data}->{$FieldName} eq '0' ) 584 ) 585 { 586 587 my $Match; 588 589 # Check if field data is a string and compare directly. 590 if ( 591 ref $Param{Data}->{$FieldName} eq '' 592 && $ActualCondition->{Fields}->{$FieldName}->{Match} eq $Param{Data}->{$FieldName} 593 ) 594 { 595 $Match = 1; 596 } 597 598 # Otherwise check if field data is and array and compare each element until 599 # one match. 600 elsif ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) { 601 602 ITEM: 603 for my $Item ( @{ $Param{Data}->{$FieldName} } ) { 604 if ( $ActualCondition->{Fields}->{$FieldName}->{Match} eq $Item ) { 605 $Match = 1; 606 last ITEM; 607 } 608 } 609 } 610 611 if ($Match) { 612 $FieldSuccess++; 613 614 # Successful check if we just need one matching Condition to make this Condition valid. 615 return 1 if $ConditionLinking eq 'or' && $CondType eq 'or'; 616 617 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 618 } 619 else { 620 $FieldFail++; 621 622 # Failed check if we have all 'and' conditions. 623 return if $ConditionLinking eq 'and' && $CondType eq 'and'; 624 625 # Try next Condition if all Condition Fields have to be true. 626 next CONDITIONNAME if $CondType eq 'and'; 627 } 628 next FIELDLNAME; 629 } 630 631 my @ArrayFields = grep { $_ =~ m{ \A \Q$FieldName\E _ \d+ \z }xms } keys %{ $Param{Data} }; 632 633 if ( @ArrayFields && defined $ActualCondition->{Fields}->{$FieldName}->{Match} ) { 634 ARRAYFIELD: 635 for my $ArrayField (@ArrayFields) { 636 next ARRAYFIELD if ref $Param{Data}->{$ArrayField} ne ''; 637 if ( $Param{Data}->{$ArrayField} ne $ActualCondition->{Fields}->{$FieldName}->{Match} ) { 638 next ARRAYFIELD; 639 } 640 641 $FieldSuccess++; 642 643 # Successful check if we just need one matching Condition to make this Condition valid. 644 return 1 if $ConditionLinking eq 'or' && $CondType eq 'or'; 645 646 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 647 next FIELDLNAME; 648 } 649 } 650 651 # No match = fail. 652 $FieldFail++; 653 654 # Failed check if we have all 'and' conditions 655 return if $ConditionLinking eq 'and' && $CondType eq 'and'; 656 657 # Try next Condition if all Condition Fields have to be true 658 next CONDITIONNAME if $CondType eq 'and'; 659 next FIELDLNAME; 660 } 661 elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Array' ) { 662 663 # 1. Go through each Condition->$ConditionName->Fields->$Field->Value (map). 664 # 2. Assign the value to $CheckValue. 665 # 3. Grep through $Data->{$Field} to find the "toCheck" value inside the Data->{$Field} Array 666 # 4. Assign all found Values to @CheckResults. 667 my $CheckValue; 668 my @CheckResults = 669 map { 670 $CheckValue = $_; 671 grep { $CheckValue eq $_ } @{ $Param{Data}->{$FieldName} } 672 } 673 @{ $ActualCondition->{Fields}->{$FieldName}->{Match} }; 674 675 # If the found amount is the same as the "toCheck" amount we succeeded 676 if ( 677 scalar @CheckResults 678 == scalar @{ $ActualCondition->{Fields}->{$FieldName}->{Match} } 679 ) 680 { 681 $FieldSuccess++; 682 683 # Successful check if we just need one matching Condition to make this Condition valid. 684 return 1 if $ConditionLinking eq 'or' && $CondType eq 'or'; 685 686 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 687 } 688 else { 689 $FieldFail++; 690 691 # Failed check if we have all 'and' conditions. 692 return if $ConditionLinking eq 'and' && $CondType eq 'and'; 693 694 # Try next Condition if all Condition Fields have to be true. 695 next CONDITIONNAME if $CondType eq 'and'; 696 } 697 next FIELDLNAME; 698 } 699 elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Hash' ) { 700 701 # if our Check doesn't contain a hash. 702 if ( ref $ActualCondition->{Fields}->{$FieldName}->{Match} ne 'HASH' ) { 703 $LogObject->Log( 704 Priority => 'error', 705 Message => 706 "Condition->$ConditionName->Fields->$FieldName Match must" 707 . " be a Hash!", 708 ); 709 return; 710 } 711 712 # If we have no data or Data isn't a hash, test failed. 713 if ( 714 !$Param{Data}->{$FieldName} 715 || ref $Param{Data}->{$FieldName} ne 'HASH' 716 ) 717 { 718 $FieldFail++; 719 next FIELDLNAME; 720 } 721 722 # Find all Data Hash values that equal to the Condition Match Values. 723 my @CheckResults = 724 grep { 725 $Param{Data}->{$FieldName}->{$_} eq 726 $ActualCondition->{Fields}->{$FieldName}->{Match}->{$_} 727 } 728 keys %{ $ActualCondition->{Fields}->{$FieldName}->{Match} }; 729 730 # If the amount of Results equals the amount of Keys in our hash this part matched. 731 if ( 732 scalar @CheckResults 733 == scalar keys %{ $ActualCondition->{Fields}->{$FieldName}->{Match} } 734 ) 735 { 736 737 $FieldSuccess++; 738 739 # Successful check if we just need one matching Condition to make this condition valid. 740 return 1 if $ConditionLinking eq 'or' && $CondType eq 'or'; 741 742 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 743 744 } 745 else { 746 $FieldFail++; 747 748 # Failed check if we have all 'and' conditions. 749 return if $ConditionLinking eq 'and' && $CondType eq 'and'; 750 751 # Try next Condition if all Condition Fields have to be true. 752 next CONDITIONNAME if $CondType eq 'and'; 753 } 754 next FIELDLNAME; 755 } 756 elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Regexp' ) 757 { 758 759 # If our Check contains anything else then a string we can't check. 760 if ( 761 !$ActualCondition->{Fields}->{$FieldName}->{Match} 762 || 763 ( 764 ref $ActualCondition->{Fields}->{$FieldName}->{Match} ne 'Regexp' 765 && ref $ActualCondition->{Fields}->{$FieldName}->{Match} ne '' 766 ) 767 ) 768 { 769 $LogObject->Log( 770 Priority => 'error', 771 Message => 772 "Condition->$ConditionName->Fields->$FieldName Match must" 773 . " be a Regular expression if Type is set to Regexp!", 774 ); 775 return; 776 } 777 778 # Precompile Regexp if is a string. 779 if ( ref $ActualCondition->{Fields}->{$FieldName}->{Match} eq '' ) { 780 my $Match = $ActualCondition->{Fields}->{$FieldName}->{Match}; 781 782 eval { 783 $ActualCondition->{Fields}->{$FieldName}->{Match} = qr{$Match}; 784 }; 785 if ($@) { 786 $LogObject->Log( 787 Priority => 'error', 788 Message => $@, 789 ); 790 return; 791 } 792 } 793 794 # Make sure there is data to compare. 795 if ( $Param{Data}->{$FieldName} ) { 796 797 my $Match; 798 799 # Check if field data is a string and compare directly. 800 if ( 801 ref $Param{Data}->{$FieldName} eq '' 802 && $Param{Data}->{$FieldName} =~ $ActualCondition->{Fields}->{$FieldName}->{Match} 803 ) 804 { 805 $Match = 1; 806 } 807 808 # Otherwise check if field data is and array and compare each element until one match. 809 elsif ( ref $Param{Data}->{$FieldName} eq 'ARRAY' ) { 810 811 ITEM: 812 for my $Item ( @{ $Param{Data}->{$FieldName} } ) { 813 if ( $Item =~ $ActualCondition->{Fields}->{$FieldName}->{Match} ) { 814 $Match = 1; 815 last ITEM; 816 } 817 } 818 } 819 820 if ($Match) { 821 $FieldSuccess++; 822 823 # Successful check if we just need one matching Condition to make this Transition valid. 824 return 1 if $ConditionLinking eq 'or' && $CondType eq 'or'; 825 826 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 827 } 828 else { 829 $FieldFail++; 830 831 # Failed check if we have all 'and' conditions. 832 return if $ConditionLinking eq 'and' && $CondType eq 'and'; 833 834 # Try next Condition if all Condition Fields have to be true. 835 next CONDITIONNAME if $CondType eq 'and'; 836 } 837 next FIELDLNAME; 838 } 839 840 my @ArrayFields = grep { $_ =~ m{ \A \Q$FieldName\E _ \d+ \z }xms } keys %{ $Param{Data} }; 841 842 if ( @ArrayFields && defined $ActualCondition->{Fields}->{$FieldName}->{Match} ) { 843 ARRAYFIELD: 844 for my $ArrayField (@ArrayFields) { 845 next ARRAYFIELD if ref $Param{Data}->{$ArrayField} ne ''; 846 if ( $Param{Data}->{$ArrayField} !~ $ActualCondition->{Fields}->{$FieldName}->{Match} ) { 847 next ARRAYFIELD; 848 } 849 850 $FieldSuccess++; 851 852 # Successful check if we just need one matching Condition to make this Condition valid. 853 return 1 if $ConditionLinking eq 'or' && $CondType eq 'or'; 854 855 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 856 next FIELDLNAME; 857 } 858 } 859 860 # No match = fail. 861 $FieldFail++; 862 863 # Failed check if we have all 'and' conditions 864 return if $ConditionLinking eq 'and' && $CondType eq 'and'; 865 866 # Try next Condition if all Condition Fields have to be true 867 next CONDITIONNAME if $CondType eq 'and'; 868 next FIELDLNAME; 869 } 870 elsif ( $ActualCondition->{Fields}->{$FieldName}->{Type} eq 'Module' ) { 871 872 # Load Validation Modules. Default location for validation modules: 873 # Kernel/GenericInterface/Event/Validation/ 874 if ( 875 !$MainObject->Require( 876 $ActualCondition->{Fields}->{$FieldName}->{Match} 877 ) 878 ) 879 { 880 $LogObject->Log( 881 Priority => 'error', 882 Message => "Can't load " 883 . $ActualCondition->{Fields}->{$FieldName}->{Type} 884 . "Module for Condition->$ConditionName->Fields->$FieldName validation!", 885 ); 886 return; 887 } 888 889 # Create new ValidateModuleObject. 890 my $ValidateModuleObject = $Kernel::OM->Get( 891 $ActualCondition->{Fields}->{$FieldName}->{Match} 892 ); 893 894 # Handle "Data" Param to ValidateModule's "Validate" subroutine. 895 if ( $ValidateModuleObject->Validate( Data => $Param{Data} ) ) { 896 $FieldSuccess++; 897 898 # Successful check if we just need one matching Condition to make this Condition valid. 899 return 1 if $ConditionLinking eq 'or' && $CondType eq 'or'; 900 901 next CONDITIONNAME if $ConditionLinking ne 'or' && $CondType eq 'or'; 902 } 903 else { 904 $FieldFail++; 905 906 # Failed check if we have all 'and' conditions. 907 return if $ConditionLinking eq 'and' && $CondType eq 'and'; 908 909 # Try next Condition if all Condition Fields have to be true. 910 next CONDITIONNAME if $CondType eq 'and'; 911 } 912 next FIELDLNAME; 913 } 914 } 915 916 # FIELDLNAME end. 917 if ( $CondType eq 'and' ) { 918 919 # If we had no failing check this condition matched. 920 if ( !$FieldFail ) { 921 922 # Successful check if we just need one matching Condition to make this Condition valid. 923 return 1 if $ConditionLinking eq 'or'; 924 $ConditionSuccess++; 925 } 926 else { 927 $ConditionFail++; 928 929 # Failed check if we have all 'and' condition.s 930 return 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 Condition valid. 940 return 1 if $ConditionLinking eq 'or'; 941 $ConditionSuccess++; 942 } 943 else { 944 $ConditionFail++; 945 946 # Failed check if we have all 'and' conditions. 947 return if $ConditionLinking eq 'and'; 948 } 949 } 950 elsif ( $CondType eq 'xor' ) 951 { 952 953 # If we had exactly one successful check, this condition matched. 954 if ( $FieldSuccess == 1 ) { 955 956 # Successful check if we just need one matching Condition to make this Condition valid. 957 return 1 if $ConditionLinking eq 'or'; 958 $ConditionSuccess++; 959 } 960 else { 961 $ConditionFail++; 962 } 963 } 964 } 965 966 # CONDITIONNAME end. 967 if ( $ConditionLinking eq 'and' ) { 968 969 # If we had no failing conditions this Condition matched. 970 return 1 if !$ConditionFail; 971 } 972 elsif ( $ConditionLinking eq 'or' ) 973 { 974 975 # If we had at least one successful condition, this condition matched. 976 return 1 if $ConditionSuccess > 0; 977 } 978 elsif ( $ConditionLinking eq 'xor' ) 979 { 980 981 # If we had exactly one successful condition, this condition matched. 982 return 1 if $ConditionSuccess == 1; 983 } 984 985 # If no condition matched till here, we failed. 986 return; 987} 988 9891; 990 991=head1 TERMS AND CONDITIONS 992 993This software is part of the OTRS project (L<https://otrs.org/>). 994 995This software comes with ABSOLUTELY NO WARRANTY. For details, see 996the enclosed file COPYING for license information (GPL). If you 997did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 998 999=cut 1000