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