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