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::Modules::AdminGenericInterfaceMappingXSLT;
10
11use strict;
12use warnings;
13
14use Kernel::System::VariableCheck qw(:all);
15use Kernel::Language qw(Translatable);
16
17our $ObjectManagerDisabled = 1;
18
19sub new {
20    my ( $Type, %Param ) = @_;
21
22    my $Self = {%Param};
23    bless( $Self, $Type );
24
25    # Set possible values handling strings.
26    $Self->{EmptyString}   = '_RegEx_EmptyString_Dont_Use_It_String_Please';
27    $Self->{DeletedString} = '_RegEx_DeletedString_Dont_Use_It_String_Please';
28
29    return $Self;
30}
31
32sub Run {
33    my ( $Self, %Param ) = @_;
34
35    my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
36
37    my $WebserviceID = $ParamObject->GetParam( Param => 'WebserviceID' ) || '';
38    my $Operation    = $ParamObject->GetParam( Param => 'Operation' )    || '';
39    my $Invoker      = $ParamObject->GetParam( Param => 'Invoker' )      || '';
40    my $Direction    = $ParamObject->GetParam( Param => 'Direction' )    || '';
41
42    my $CommunicationType = IsStringWithData($Operation) ? 'Provider'  : 'Requester';
43    my $ActionType        = IsStringWithData($Operation) ? 'Operation' : 'Invoker';
44    my $Action = $Operation || $Invoker;
45
46    # Set mapping direction for display.
47    my $MappingDirection = $Direction eq 'MappingOutbound'
48        ? Translatable('XSLT Mapping for Outgoing Data')
49        : Translatable('XSLT Mapping for Incoming Data');
50
51    # Get configured Actions.
52    my $ActionsConfig = $Kernel::OM->Get('Kernel::Config')->Get( 'GenericInterface::' . $ActionType . '::Module' );
53
54    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
55
56    # Make sure required libraries (XML::LibXML and XML::LibXSLT) are installed.
57    LIBREQUIRED:
58    for my $LibRequired (qw(XML::LibXML XML::LibXSLT)) {
59        my $LibFound = $Kernel::OM->Get('Kernel::System::Main')->Require(
60            $LibRequired,
61        );
62        next LIBREQUIRED if $LibFound;
63
64        return $LayoutObject->ErrorScreen(
65            Message => $LayoutObject->{LanguageObject}->Translate( 'Could not find required library %s', $LibRequired ),
66        );
67    }
68
69    # Check for valid action backend.
70    if ( !IsHashRefWithData($ActionsConfig) ) {
71        return $LayoutObject->ErrorScreen(
72            Message => $LayoutObject->{LanguageObject}
73                ->Translate( 'Could not get registered configuration for action type %s', $ActionType ),
74        );
75    }
76
77    # Check for WebserviceID.
78    if ( !$WebserviceID ) {
79        return $LayoutObject->ErrorScreen(
80            Message => Translatable('Need WebserviceID!'),
81        );
82    }
83
84    my $WebserviceObject = $Kernel::OM->Get('Kernel::System::GenericInterface::Webservice');
85
86    # Get web service con configuration.
87    my $WebserviceData = $WebserviceObject->WebserviceGet( ID => $WebserviceID );
88
89    # Check for valid web service configuration.
90    if ( !IsHashRefWithData($WebserviceData) ) {
91        return $LayoutObject->ErrorScreen(
92            Message =>
93                $LayoutObject->{LanguageObject}->Translate( 'Could not get data for WebserviceID %s', $WebserviceID ),
94        );
95    }
96
97    # Get the action type (back-end),
98    my $ActionBackend = $WebserviceData->{Config}->{$CommunicationType}->{$ActionType}->{$Action}->{'Type'};
99
100    # Check for valid action backend.
101    if ( !$ActionBackend ) {
102        return $LayoutObject->ErrorScreen(
103            Message =>
104                $LayoutObject->{LanguageObject}->Translate( 'Could not get backend for %s %s', $ActionType, $Action ),
105        );
106    }
107
108    # Get the configuration dialog for the action.
109    my $ActionFrontendModule = $ActionsConfig->{$ActionBackend}->{'ConfigDialog'};
110
111    my $WebserviceName = $WebserviceData->{Name};
112
113    # ------------------------------------------------------------ #
114    # sub-action Change: load web service and show edit screen
115    # ------------------------------------------------------------ #
116    if ( $Self->{Subaction} eq 'Change' ) {
117
118        # Recreate structure for edit.
119        my %Mapping;
120        my $MappingConfig = $WebserviceData->{Config}->{$CommunicationType}->
121            {$ActionType}->{$Action}->{$Direction}->{Config};
122
123        $Mapping{Template}              = $MappingConfig->{Template};
124        $Mapping{DataInclude}           = $MappingConfig->{DataInclude};
125        $Mapping{PreRegExFilter}        = $MappingConfig->{PreRegExFilter};
126        $Mapping{PreRegExValueCounter}  = $MappingConfig->{PreRegExValueCounter};
127        $Mapping{PostRegExFilter}       = $MappingConfig->{PostRegExFilter};
128        $Mapping{PostRegExValueCounter} = $MappingConfig->{PostRegExValueCounter};
129
130        return $Self->_ShowEdit(
131            %Param,
132            WebserviceID         => $WebserviceID,
133            WebserviceName       => $WebserviceName,
134            WebserviceData       => \%Mapping,
135            Operation            => $Operation,
136            Invoker              => $Invoker,
137            Direction            => $Direction,
138            MappingDirection     => $MappingDirection,
139            CommunicationType    => $CommunicationType,
140            ActionType           => $ActionType,
141            Action               => $Action,
142            ActionFrontendModule => $ActionFrontendModule,
143            Subaction            => 'Change',
144        );
145    }
146
147    # ------------------------------------------------------------ #
148    # sub-action ChangeAction: write config and return to overview
149    # ------------------------------------------------------------ #
150    else {
151
152        # Challenge token check for write action.
153        $LayoutObject->ChallengeTokenCheck();
154
155        # Get parameter from web browser.
156        my $GetParam = $Self->_GetParams();
157
158        # If there is an error return to edit screen.
159        if ( $GetParam->{Error} ) {
160            return $Self->_ShowEdit(
161                %Param,
162                WebserviceID         => $WebserviceID,
163                WebserviceName       => $WebserviceName,
164                WebserviceData       => $GetParam,
165                Operation            => $Operation,
166                Invoker              => $Invoker,
167                Direction            => $Direction,
168                MappingDirection     => $MappingDirection,
169                CommunicationType    => $CommunicationType,
170                ActionType           => $ActionType,
171                Action               => $Action,
172                ActionFrontendModule => $ActionFrontendModule,
173                Subaction            => 'Change',
174            );
175        }
176
177        my %NewMapping;
178        $NewMapping{Template}              = $GetParam->{Template};
179        $NewMapping{DataInclude}           = $GetParam->{DataInclude};
180        $NewMapping{PreRegExFilter}        = $GetParam->{PreRegExFilter};
181        $NewMapping{PreRegExValueCounter}  = $GetParam->{PreRegExValueCounter};
182        $NewMapping{PostRegExFilter}       = $GetParam->{PostRegExFilter};
183        $NewMapping{PostRegExValueCounter} = $GetParam->{PostRegExValueCounter};
184
185        # Set new mapping.
186        $WebserviceData->{Config}->{$CommunicationType}->{$ActionType}->{$Action}->{$Direction}->{Config}
187            = \%NewMapping;
188
189        # Otherwise save configuration and return to overview screen.
190        my $Success = $WebserviceObject->WebserviceUpdate(
191            ID      => $WebserviceID,
192            Name    => $WebserviceData->{Name},
193            Config  => $WebserviceData->{Config},
194            ValidID => $WebserviceData->{ValidID},
195            UserID  => $Self->{UserID},
196        );
197
198        # Check for successful web service update.
199        if ( !$Success ) {
200            return $LayoutObject->ErrorScreen(
201                Message => $LayoutObject->{LanguageObject}
202                    ->Translate( 'Could not update configuration data for WebserviceID %s', $WebserviceID ),
203            );
204        }
205
206        # If the user would like to continue editing the invoker config, just redirect to the edit screen.
207        my $RedirectURL;
208        if (
209            defined $ParamObject->GetParam( Param => 'ContinueAfterSave' )
210            && ( $ParamObject->GetParam( Param => 'ContinueAfterSave' ) eq '1' )
211            )
212        {
213
214            $RedirectURL =
215                'Action='
216                . $Self->{Action}
217                . ';Subaction=Change;WebserviceID='
218                . $WebserviceID
219                . ";$ActionType="
220                . $LayoutObject->LinkEncode($Action)
221                . ';Direction='
222                . $Direction
223                . ';';
224        }
225        else {
226
227            # Otherwise return to overview.
228            $RedirectURL =
229                'Action='
230                . $ActionFrontendModule
231                . ';Subaction=Change;'
232                . ";$ActionType="
233                . $LayoutObject->LinkEncode($Action)
234                . ';WebserviceID='
235                . $WebserviceID
236                . ';';
237        }
238
239        return $LayoutObject->Redirect(
240            OP => $RedirectURL,
241        );
242    }
243}
244
245sub _ShowEdit {
246    my ( $Self, %Param ) = @_;
247
248    # Set action for go back button.
249    $Param{LowerCaseActionType} = lc $Param{ActionType};
250
251    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
252
253    my $Output = $LayoutObject->Header();
254    $Output .= $LayoutObject->NavigationBar();
255
256    my $MappingConfig = $Param{WebserviceData};
257    my %Error;
258    if ( defined $Param{WebserviceData}->{Error} ) {
259        %Error = %{ $Param{WebserviceData}->{Error} };
260    }
261
262    # Add rich text editor config.
263    if ( $LayoutObject->{BrowserRichText} ) {
264        $LayoutObject->SetRichTextParameters(
265            Data => {
266                %Param,
267                RichTextHeight => '600',
268                RichTextWidth  => '99%',
269                RichTextType   => 'CodeMirror',
270            },
271        );
272    }
273
274    # Render pre regex filters.
275    $Self->_RegExFiltersOutput(
276        %{$MappingConfig},
277        Type => 'Pre',
278    );
279
280    my %DataIncludeOptionMap = (
281        Requester => {
282            MappingOutbound => [
283                {
284                    Key   => 'RequesterRequestInput',
285                    Value => Translatable('Outgoing request data before processing (RequesterRequestInput)'),
286                },
287                {
288                    Key   => 'RequesterRequestPrepareOutput',
289                    Value => Translatable('Outgoing request data before mapping (RequesterRequestPrepareOutput)'),
290                },
291            ],
292            MappingInbound => [
293                {
294                    Key   => 'RequesterRequestInput',
295                    Value => Translatable('Outgoing request data before processing (RequesterRequestInput)'),
296                },
297                {
298                    Key   => 'RequesterRequestPrepareOutput',
299                    Value => Translatable('Outgoing request data before mapping (RequesterRequestPrepareOutput)'),
300                },
301                {
302                    Key   => 'RequesterRequestMapOutput',
303                    Value => Translatable('Outgoing request data after mapping (RequesterRequestMapOutput)'),
304                },
305                {
306                    Key   => 'RequesterResponseInput',
307                    Value => Translatable('Incoming response data before mapping (RequesterResponseInput)'),
308                },
309                {
310                    Key => 'RequesterErrorHandlingOutput',
311                    Value =>
312                        Translatable('Outgoing error handler data after error handling (RequesterErrorHandlingOutput)'),
313                },
314            ],
315        },
316        Provider => {
317            MappingOutbound => [
318                {
319                    Key   => 'ProviderRequestInput',
320                    Value => Translatable('Incoming request data before mapping (ProviderRequestInput)'),
321                },
322                {
323                    Key   => 'ProviderRequestMapOutput',
324                    Value => Translatable('Incoming request data after mapping (ProviderRequestMapOutput)'),
325                },
326                {
327                    Key   => 'ProviderResponseInput',
328                    Value => Translatable('Outgoing response data before mapping (ProviderResponseInput)'),
329                },
330                {
331                    Key => 'ProviderErrorHandlingOutput',
332                    Value =>
333                        Translatable('Outgoing error handler data after error handling (ProviderErrorHandlingOutput)'),
334                },
335            ],
336            MappingInbound => [
337                {
338                    Key   => 'ProviderRequestInput',
339                    Value => Translatable('Incoming request data before mapping (ProviderRequestInput)'),
340                },
341            ],
342        },
343    );
344    $Param{DataIncludeSelect} = $LayoutObject->BuildSelection(
345        Data         => $DataIncludeOptionMap{ $Param{CommunicationType} }->{ $Param{Direction} },
346        Name         => 'DataInclude',
347        SelectedID   => $MappingConfig->{DataInclude},
348        PossibleNone => 1,
349        Translation  => 1,
350        Multiple     => 1,
351        Class        => 'Modernize W50pc',
352    );
353
354    $LayoutObject->Block(
355        Name => 'ConfigBlock',
356        Data => {},
357    );
358    $LayoutObject->Block(
359        Name => 'ConfigBlockTemplate',
360        Data => {
361            %Param,
362            Template      => $MappingConfig->{Template},
363            TemplateError => $Error{Template} || '',
364        },
365    );
366
367    # Render post regex filters.
368    $Self->_RegExFiltersOutput(
369        %{$MappingConfig},
370        Type => 'Post',
371    );
372
373    $Output .= $LayoutObject->Output(
374        TemplateFile => 'AdminGenericInterfaceMappingXSLT',
375        Data         => {
376            %Param,
377        },
378    );
379
380    $Output .= $LayoutObject->Footer();
381    return $Output;
382}
383
384sub _GetParams {
385    my ( $Self, %Param ) = @_;
386
387    my $GetParam;
388
389    my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
390
391    # Get parameters from web browser.
392    $GetParam->{Template} = $ParamObject->GetParam( Param => 'Template' ) || '';
393    my @DataInclude = $ParamObject->GetArray( Param => 'DataInclude' );
394    $GetParam->{DataInclude} = \@DataInclude;
395
396    # Check validity.
397    my $LibXML  = XML::LibXML->new();
398    my $LibXSLT = XML::LibXSLT->new();
399    my ( $StyleDoc, $StyleSheet );
400    eval {
401        $StyleDoc = XML::LibXML->load_xml(
402            string   => $GetParam->{Template},
403            no_cdata => 1,
404        );
405    };
406    if ( !$StyleDoc ) {
407        $GetParam->{Error}->{Template} = 'ServerError';
408    }
409    eval {
410        my $LibXSLT = XML::LibXSLT->new();
411        $StyleSheet = $LibXSLT->parse_stylesheet($StyleDoc);
412    };
413    if ( !$StyleSheet ) {
414        $GetParam->{Error}->{Template} = 'ServerError';
415    }
416
417    # Get RegEx params.
418    my %RegExFilterConfig;
419    TYPE:
420    for my $Type (qw(Pre Post)) {
421        my $ValueCounter = $ParamObject->GetParam( Param => $Type . 'ValueCounter' ) // 0;
422        next TYPE if !$ValueCounter;
423
424        my $EmptyValueCounter = 0;
425        my @RegExConfig;
426        VALUEINDEX:
427        for my $ValueIndex ( 1 .. $ValueCounter ) {
428            my $Key = $ParamObject->GetParam( Param => $Type . 'Key' . '_' . $ValueIndex ) // '';
429
430            # Check if key was deleted by the user and skip it.
431            next VALUEINDEX if $Key eq $Self->{DeletedString};
432
433            # Check if the original value is empty.
434            if ( !IsStringWithData($Key) ) {
435
436                # Change the empty value to a predefined string.
437                $Key = $Self->{EmptyString} . $EmptyValueCounter++;
438                $GetParam->{Error}->{ $Type . 'RegExFilter' }->{$Key} = 1;
439            }
440
441            push @RegExConfig, {
442                Search  => $Key,
443                Replace => $ParamObject->GetParam( Param => $Type . 'Value' . '_' . $ValueIndex ) // '',
444            };
445        }
446
447        $GetParam->{ $Type . 'RegExFilter' }       = \@RegExConfig;
448        $GetParam->{ $Type . 'RegExValueCounter' } = scalar @RegExConfig;
449    }
450
451    return $GetParam;
452}
453
454sub _RegExFiltersOutput {
455    my ( $Self, %Param ) = @_;
456
457    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
458
459    my @RegExFilter;
460    if ( IsArrayRefWithData( $Param{ $Param{Type} . 'RegExFilter' } ) ) {
461        @RegExFilter = @{ $Param{ $Param{Type} . 'RegExFilter' } };
462    }
463
464    $LayoutObject->Block(
465        Name => 'ConfigBlock',
466        Data => {},
467    );
468    $LayoutObject->Block(
469        Name => 'ConfigBlockRegExFilter',
470        Data => {
471            Type          => $Param{Type},
472            ValueCounter  => $Param{ $Param{Type} . 'RegExValueCounter' } // 0,
473            DeletedString => $Self->{DeletedString},
474            Collapsed     => !@RegExFilter ? 'Collapsed' : undef,
475        },
476    );
477
478    # Create the possible values template.
479    $LayoutObject->Block(
480        Name => 'ValueTemplate',
481        Data => {
482            %Param,
483        },
484    );
485
486    return 1 if !@RegExFilter;
487
488    # Output the possible entries and errors within (if any).
489    my $ValueCounter = 1;
490    for my $RegEx (@RegExFilter) {
491
492        # Needed for server side validation.
493        my $KeyError;
494        my $KeyErrorStrg;
495
496        # To set the correct original value.
497        my $KeyClone = $RegEx->{Search};
498
499        # Check for errors.
500        if ( $Param{Error}->{ $Param{Type} . 'RegExFilter' }->{ $RegEx->{Search} } ) {
501
502            # If the original value was empty it has been changed in _GetParams to a predefined
503            #   string and need to be set to empty again.
504            $KeyClone = '';
505
506            # Set the error class.
507            $KeyError     = 'ServerError';
508            $KeyErrorStrg = Translatable('This field is required.');
509        }
510
511        # Create a value map row.
512        $LayoutObject->Block(
513            Name => 'ValueRow',
514            Data => {
515                Type         => $Param{Type},
516                KeyError     => $KeyError,
517                KeyErrorStrg => $KeyErrorStrg || Translatable('This field is required.'),
518                Key          => $KeyClone,
519                ValueCounter => $ValueCounter,
520                Value        => $RegEx->{Replace},
521            },
522        );
523    }
524    continue {
525        ++$ValueCounter;
526    }
527
528    return 1;
529}
530
5311;
532