1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::System::CustomerUser; 10 11use strict; 12use warnings; 13 14use Kernel::System::VariableCheck qw(:all); 15 16use parent qw(Kernel::System::EventHandler); 17 18our @ObjectDependencies = ( 19 'Kernel::Config', 20 'Kernel::Language', 21 'Kernel::System::Cache', 22 'Kernel::System::CustomerCompany', 23 'Kernel::System::DB', 24 'Kernel::System::DynamicField', 25 'Kernel::System::DynamicField::Backend', 26 'Kernel::System::Encode', 27 'Kernel::System::Log', 28 'Kernel::System::Main', 29 'Kernel::System::Valid', 30); 31 32=head1 NAME 33 34Kernel::System::CustomerUser - customer user lib 35 36=head1 DESCRIPTION 37 38All customer user functions. E. g. to add and update customer users. 39 40=head1 PUBLIC INTERFACE 41 42=head2 new() 43 44Don't use the constructor directly, use the ObjectManager instead: 45 46 my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); 47 48=cut 49 50sub new { 51 my ( $Type, %Param ) = @_; 52 53 # allocate new hash for object 54 my $Self = {}; 55 bless( $Self, $Type ); 56 57 $Self->{CacheType} = 'CustomerUser'; 58 $Self->{CacheTTL} = 60 * 60 * 24 * 20; 59 60 # get config object 61 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 62 63 # load generator customer preferences module 64 my $GeneratorModule = $ConfigObject->Get('CustomerPreferences')->{Module} 65 || 'Kernel::System::CustomerUser::Preferences::DB'; 66 67 # get main object 68 my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); 69 70 if ( $MainObject->Require($GeneratorModule) ) { 71 $Self->{PreferencesObject} = $GeneratorModule->new(); 72 } 73 74 # load customer user backend module 75 SOURCE: 76 for my $Count ( '', 1 .. 10 ) { 77 78 next SOURCE if !$ConfigObject->Get("CustomerUser$Count"); 79 80 my $GenericModule = $ConfigObject->Get("CustomerUser$Count")->{Module}; 81 if ( !$MainObject->Require($GenericModule) ) { 82 $MainObject->Die("Can't load backend module $GenericModule! $@"); 83 } 84 85 $Self->{"CustomerUser$Count"} = $GenericModule->new( 86 Count => $Count, 87 PreferencesObject => $Self->{PreferencesObject}, 88 CustomerUserMap => $ConfigObject->Get("CustomerUser$Count"), 89 ); 90 } 91 92 # init of event handler 93 $Self->EventHandlerInit( 94 Config => 'CustomerUser::EventModulePost', 95 ); 96 97 return $Self; 98} 99 100=head2 CustomerSourceList() 101 102return customer source list 103 104 my %List = $CustomerUserObject->CustomerSourceList( 105 ReadOnly => 0 # optional, 1 returns only RO backends, 0 returns writable, if not passed returns all backends 106 ); 107 108=cut 109 110sub CustomerSourceList { 111 my ( $Self, %Param ) = @_; 112 113 # get config object 114 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 115 116 my %Data; 117 SOURCE: 118 for my $Count ( '', 1 .. 10 ) { 119 120 next SOURCE if !$ConfigObject->Get("CustomerUser$Count"); 121 if ( defined $Param{ReadOnly} ) { 122 my $CustomerBackendConfig = $ConfigObject->Get("CustomerUser$Count"); 123 if ( $Param{ReadOnly} ) { 124 next SOURCE if !$CustomerBackendConfig->{ReadOnly} || $CustomerBackendConfig->{Module} !~ /LDAP/i; 125 } 126 else { 127 next SOURCE if $CustomerBackendConfig->{ReadOnly} || $CustomerBackendConfig->{Module} =~ /LDAP/i; 128 } 129 } 130 $Data{"CustomerUser$Count"} = $ConfigObject->Get("CustomerUser$Count")->{Name} 131 || "No Name $Count"; 132 } 133 return %Data; 134} 135 136=head2 CustomerSearch() 137 138to search users 139 140 # text search 141 my %List = $CustomerUserObject->CustomerSearch( 142 Search => '*some*', # also 'hans+huber' possible 143 Valid => 1, # (optional) default 1 144 Limit => 100, # (optional) overrides limit of the config 145 ); 146 147 # username search 148 my %List = $CustomerUserObject->CustomerSearch( 149 UserLogin => '*some*', 150 Valid => 1, # (optional) default 1 151 ); 152 153 # email search 154 my %List = $CustomerUserObject->CustomerSearch( 155 PostMasterSearch => 'email@example.com', 156 Valid => 1, # (optional) default 1 157 ); 158 159 # search by CustomerID 160 my %List = $CustomerUserObject->CustomerSearch( 161 CustomerID => 'CustomerID123', 162 Valid => 1, # (optional) default 1 163 ); 164 165=cut 166 167sub CustomerSearch { 168 my ( $Self, %Param ) = @_; 169 170 # remove leading and ending spaces 171 if ( $Param{Search} ) { 172 $Param{Search} =~ s/^\s+//; 173 $Param{Search} =~ s/\s+$//; 174 } 175 176 # Get dynamic fiekd object. 177 my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); 178 179 my $DynamicFieldConfigs = $DynamicFieldObject->DynamicFieldListGet( 180 ObjectType => 'CustomerUser', 181 Valid => 1, 182 ); 183 184 my %DynamicFieldLookup = map { $_->{Name} => $_ } @{$DynamicFieldConfigs}; 185 186 # Get dynamic field backend object. 187 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 188 189 my %Data; 190 SOURCE: 191 for my $Count ( '', 1 .. 10 ) { 192 193 next SOURCE if !$Self->{"CustomerUser$Count"}; 194 195 # search dynamic field values, if configured 196 my $Map = $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map}; 197 if ( IsArrayRefWithData($Map) ) { 198 199 # fetch dynamic field names that are configured in Map 200 # only these will be considered for any other search config 201 # [ 'DynamicField_Name_X', undef, 'Name_X', 0, 0, 'dynamic_field', undef, 0, undef, undef, ], 202 my %DynamicFieldNames = map { $_->[2] => 1 } grep { $_->[5] eq 'dynamic_field' } @{$Map}; 203 204 if ( IsHashRefWithData( \%DynamicFieldNames ) ) { 205 my $FoundDynamicFieldObjectIDs; 206 my $SearchFields; 207 my $SearchParam; 208 209 # check which of the dynamic fields configured in Map are also 210 # configured in SearchFields 211 212 # param Search 213 if ( defined $Param{Search} && length $Param{Search} ) { 214 $SearchFields = $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{CustomerUserSearchFields}; 215 $SearchParam = $Param{Search}; 216 } 217 218 # param PostMasterSearch 219 elsif ( defined $Param{PostMasterSearch} && length $Param{PostMasterSearch} ) { 220 $SearchFields 221 = $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{CustomerUserPostMasterSearchFields}; 222 $SearchParam = $Param{PostMasterSearch}; 223 } 224 225 # search dynamic field values 226 if ( IsArrayRefWithData($SearchFields) ) { 227 my @SearchDynamicFieldNames = grep { exists $DynamicFieldNames{$_} } @{$SearchFields}; 228 my @SearchDynamicFieldIDs; 229 230 my %FoundDynamicFieldObjectIDs; 231 FIELDNAME: 232 for my $FieldName (@SearchDynamicFieldNames) { 233 234 my $DynamicFieldConfig = $DynamicFieldLookup{$FieldName}; 235 236 next FIELDNAME if !IsHashRefWithData($DynamicFieldConfig); 237 238 my $DynamicFieldValues = $DynamicFieldBackendObject->ValueSearch( 239 DynamicFieldConfig => $DynamicFieldConfig, 240 Search => $SearchParam, 241 ); 242 243 if ( IsArrayRefWithData($DynamicFieldValues) ) { 244 for my $DynamicFieldValue ( @{$DynamicFieldValues} ) { 245 $FoundDynamicFieldObjectIDs{ $DynamicFieldValue->{ObjectID} } = 1; 246 } 247 } 248 } 249 250 $FoundDynamicFieldObjectIDs = [ keys %FoundDynamicFieldObjectIDs ]; 251 } 252 253 # execute backend search for found object IDs 254 # this data is being merged with the following CustomerSearch call 255 if ( IsArrayRefWithData($FoundDynamicFieldObjectIDs) ) { 256 257 my $ObjectNames = $DynamicFieldObject->ObjectMappingGet( 258 ObjectID => $FoundDynamicFieldObjectIDs, 259 ObjectType => 'CustomerUser', 260 ); 261 262 OBJECTNAME: 263 for my $ObjectName ( values %{$ObjectNames} ) { 264 next OBJECTNAME if exists $Data{$ObjectName}; 265 266 my %SearchParam = %Param; 267 delete $SearchParam{Search}; 268 delete $SearchParam{PostMasterSearch}; 269 270 $SearchParam{UserLogin} = $ObjectName; 271 272 my %SubData = $Self->{"CustomerUser$Count"}->CustomerSearch(%SearchParam); 273 274 # UserLogin search does a wild-card search, but in this case only the 275 # exact matching user login is relevant 276 if ( IsHashRefWithData( \%SubData ) && exists $SubData{$ObjectName} ) { 277 %Data = ( 278 $ObjectName => $SubData{$ObjectName}, 279 %Data 280 ); 281 } 282 } 283 } 284 } 285 } 286 287 # get customer search result of backend and merge it 288 my %SubData = $Self->{"CustomerUser$Count"}->CustomerSearch(%Param); 289 290 %Data = ( %SubData, %Data ); 291 } 292 return %Data; 293} 294 295=head2 CustomerSearchDetail() 296 297To find customer user in the system. 298 299The search criteria are logically AND connected. 300When a list is passed as criteria, the individual members are OR connected. 301When an undef or a reference to an empty array is passed, then the search criteria 302is ignored. 303 304Returns either a list, as an arrayref, or a count of found customer user ids. 305The count of results is returned when the parameter C<Result = 'COUNT'> is passed. 306 307 my $CustomerUserIDsRef = $CustomerUserObject->CustomerSearchDetail( 308 309 # all search fields possible which are defined in CustomerUser::EnhancedSearchFields 310 UserLogin => 'example*', # (optional) 311 UserFirstname => 'Firstn*', # (optional) 312 313 # special parameters 314 CustomerCompanySearchCustomerIDs => [ 'example.com' ], # (optional) 315 ExcludeUserLogins => [ 'example', 'doejohn' ], # (optional) 316 317 # array parameters are used with logical OR operator (all values are possible which 318 are defined in the config selection hash for the field) 319 UserCountry => [ 'Austria', 'Germany', ], # (optional) 320 321 # DynamicFields 322 # At least one operator must be specified. Operators will be connected with AND, 323 # values in an operator with OR. 324 # You can also pass more than one argument to an operator: ['value1', 'value2'] 325 DynamicField_FieldNameX => { 326 Equals => 123, 327 Like => 'value*', # "equals" operator with wildcard support 328 GreaterThan => '2001-01-01 01:01:01', 329 GreaterThanEquals => '2001-01-01 01:01:01', 330 SmallerThan => '2002-02-02 02:02:02', 331 SmallerThanEquals => '2002-02-02 02:02:02', 332 } 333 334 OrderBy => [ 'UserLogin', 'UserCustomerID' ], # (optional) 335 # ignored if the result type is 'COUNT' 336 # default: [ 'UserLogin' ] 337 # (all search fields possible which are defined in 338 CustomerUser::EnhancedSearchFields) 339 340 # Additional information for OrderBy: 341 # The OrderByDirection can be specified for each OrderBy attribute. 342 # The pairing is made by the array indices. 343 344 OrderByDirection => [ 'Down', 'Up' ], # (optional) 345 # ignored if the result type is 'COUNT' 346 # (Down | Up) Default: [ 'Down' ] 347 348 Result => 'ARRAY' || 'COUNT', # (optional) 349 # default: ARRAY, returns an array of change ids 350 # COUNT returns a scalar with the number of found changes 351 352 Limit => 100, # (optional) 353 # ignored if the result type is 'COUNT' 354 ); 355 356Returns: 357 358Result: 'ARRAY' 359 360 @CustomerUserIDs = ( 1, 2, 3 ); 361 362Result: 'COUNT' 363 364 $CustomerUserIDs = 10; 365 366=cut 367 368sub CustomerSearchDetail { 369 my ( $Self, %Param ) = @_; 370 371 # get all general search fields (without a restriction to a source) 372 my @AllSearchFields = $Self->CustomerUserSearchFields(); 373 374 # generate a hash with the customer user sources which must be searched 375 my %SearchCustomerUserSources; 376 377 SOURCE: 378 for my $Count ( '', 1 .. 10 ) { 379 next SOURCE if !$Self->{"CustomerUser$Count"}; 380 381 # get the search fields for the current source 382 my @SourceSearchFields = $Self->CustomerUserSearchFields( 383 Source => "CustomerUser$Count", 384 ); 385 my %LookupSourceSearchFields = map { $_->{Name} => 1 } @SourceSearchFields; 386 387 # check if all search param exists in the search fields from the current source 388 SEARCHFIELD: 389 for my $SearchField (@AllSearchFields) { 390 391 next SEARCHFIELD if !$Param{ $SearchField->{Name} }; 392 393 next SOURCE if !$LookupSourceSearchFields{ $SearchField->{Name} }; 394 } 395 $SearchCustomerUserSources{"CustomerUser$Count"} = \@SourceSearchFields; 396 } 397 398 # set the default behaviour for the return type 399 $Param{Result} ||= 'ARRAY'; 400 401 if ( $Param{Result} eq 'COUNT' ) { 402 403 my $IDsCount = 0; 404 405 SOURCE: 406 for my $Source ( sort keys %SearchCustomerUserSources ) { 407 next SOURCE if !$Self->{$Source}; 408 409 my $SubIDsCount = $Self->{$Source}->CustomerSearchDetail( 410 %Param, 411 SearchFields => $SearchCustomerUserSources{$Source}, 412 ); 413 414 return if !defined $SubIDsCount; 415 416 $IDsCount += $SubIDsCount || 0; 417 } 418 return $IDsCount; 419 } 420 else { 421 422 my @IDs; 423 424 my $ResultCount = 0; 425 426 SOURCE: 427 for my $Source ( sort keys %SearchCustomerUserSources ) { 428 next SOURCE if !$Self->{$Source}; 429 430 my $SubIDs = $Self->{$Source}->CustomerSearchDetail( 431 %Param, 432 SearchFields => $SearchCustomerUserSources{$Source}, 433 ); 434 435 return if !defined $SubIDs; 436 437 next SOURCE if !IsArrayRefWithData($SubIDs); 438 439 push @IDs, @{$SubIDs}; 440 441 $ResultCount++; 442 } 443 444 # if we have more then one search results from diffrent sources, we need a resorting 445 # because of the merged single results 446 if ( $ResultCount > 1 ) { 447 448 my @UserDataList; 449 450 for my $ID (@IDs) { 451 452 my %UserData = $Self->CustomerUserDataGet( 453 User => $ID, 454 ); 455 push @UserDataList, \%UserData; 456 } 457 458 my $OrderBy = 'UserLogin'; 459 if ( IsArrayRefWithData( $Param{OrderBy} ) ) { 460 $OrderBy = $Param{OrderBy}->[0]; 461 } 462 463 if ( IsArrayRefWithData( $Param{OrderByDirection} ) && $Param{OrderByDirection}->[0] eq 'Up' ) { 464 @UserDataList = sort { lc( $a->{$OrderBy} ) cmp lc( $b->{$OrderBy} ) } @UserDataList; 465 } 466 else { 467 @UserDataList = sort { lc( $b->{$OrderBy} ) cmp lc( $a->{$OrderBy} ) } @UserDataList; 468 } 469 470 if ( $Param{Limit} && scalar @UserDataList > $Param{Limit} ) { 471 splice @UserDataList, $Param{Limit}; 472 } 473 474 @IDs = map { $_->{UserLogin} } @UserDataList; 475 } 476 477 return \@IDs; 478 } 479} 480 481=head2 CustomerUserSearchFields() 482 483Get a list of the defined search fields (optional only the relevant fields for the given source). 484 485 my @SeachFields = $CustomerUserObject->CustomerUserSearchFields( 486 Source => 'CustomerUser', # optional, but important in the CustomerSearchDetail to get the right database fields 487 ); 488 489Returns an array of hash references. 490 491 @SeachFields = ( 492 { 493 Name => 'UserEmail', 494 Label => 'Email', 495 Type => 'Input', 496 DatabaseField => 'mail', 497 }, 498 { 499 Name => 'UserCountry', 500 Label => 'Country', 501 Type => 'Selection', 502 SelectionsData => { 503 'Germany' => 'Germany', 504 'United Kingdom' => 'United Kingdom', 505 'United States' => 'United States', 506 ... 507 }, 508 DatabaseField => 'country', 509 }, 510 { 511 Name => 'DynamicField_SkypeAccountName', 512 Label => '', 513 Type => 'DynamicField', 514 DatabaseField => 'SkypeAccountName', 515 }, 516 ); 517 518=cut 519 520sub CustomerUserSearchFields { 521 my ( $Self, %Param ) = @_; 522 523 # Get the search fields from all customer user maps (merge from all maps together). 524 my @SearchFields; 525 526 my %SearchFieldsExists; 527 528 SOURCE: 529 for my $Count ( '', 1 .. 10 ) { 530 next SOURCE if !$Self->{"CustomerUser$Count"}; 531 next SOURCE if $Param{Source} && $Param{Source} ne "CustomerUser$Count"; 532 533 ENTRY: 534 for my $Entry ( @{ $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map} } ) { 535 536 my $SearchFieldName = $Entry->[0]; 537 538 next ENTRY if $SearchFieldsExists{$SearchFieldName}; 539 next ENTRY if $SearchFieldName =~ m{(Password|Pw)\d*$}smxi; 540 541 # Remeber the already collected search field name. 542 $SearchFieldsExists{$SearchFieldName} = 1; 543 544 my %FieldConfig = $Self->GetFieldConfig( 545 FieldName => $SearchFieldName, 546 Source => $Param{Source}, # to get the right database field for the given source 547 ); 548 549 next SEARCHFIELDNAME if !%FieldConfig; 550 551 my %SearchFieldData = ( 552 %FieldConfig, 553 Name => $SearchFieldName, 554 ); 555 556 my %SelectionsData = $Self->GetFieldSelections( 557 FieldName => $SearchFieldName, 558 ); 559 560 if ( $SearchFieldData{StorageType} eq 'dynamic_field' ) { 561 $SearchFieldData{Type} = 'DynamicField'; 562 } 563 elsif (%SelectionsData) { 564 $SearchFieldData{Type} = 'Selection'; 565 $SearchFieldData{SelectionsData} = \%SelectionsData; 566 } 567 else { 568 $SearchFieldData{Type} = 'Input'; 569 } 570 571 push @SearchFields, \%SearchFieldData; 572 } 573 } 574 575 return @SearchFields; 576} 577 578=head2 GetFieldConfig() 579 580This function collect some field config information from the customer user map. 581 582 my %FieldConfig = $CustomerUserObject->GetFieldConfig( 583 FieldName => 'UserEmail', 584 Source => 'CustomerUser', # optional 585 ); 586 587Returns some field config information: 588 589 my %FieldConfig = ( 590 Label => 'Email', 591 DatabaseField => 'email', 592 StorageType => 'var', 593 ); 594 595=cut 596 597sub GetFieldConfig { 598 my ( $Self, %Param ) = @_; 599 600 if ( !$Param{FieldName} ) { 601 $Kernel::OM->Get('Kernel::System::Log')->Log( 602 Priority => 'error', 603 Message => "Need FieldName!" 604 ); 605 return; 606 } 607 608 SOURCE: 609 for my $Count ( '', 1 .. 10 ) { 610 next SOURCE if !$Self->{"CustomerUser$Count"}; 611 next SOURCE if $Param{Source} && $Param{Source} ne "CustomerUser$Count"; 612 613 # Search the right field and return some config information from the field. 614 ENTRY: 615 for my $Entry ( @{ $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map} } ) { 616 next ENTRY if $Param{FieldName} ne $Entry->[0]; 617 618 my %FieldConfig = ( 619 Label => $Entry->[1], 620 DatabaseField => $Entry->[2], 621 StorageType => $Entry->[5], 622 ); 623 624 return %FieldConfig; 625 } 626 } 627 628 return; 629} 630 631=head2 GetFieldSelections() 632 633This function collect the selections for the given field name, if the field has some selections. 634 635 my %SelectionsData = $CustomerUserObject->GetFieldSelections( 636 FieldName => 'UserTitle', 637 ); 638 639Returns the selections for the given field name (merged from all sources) or a empty hash: 640 641 my %SelectionData = ( 642 'Mr.' => 'Mr.', 643 'Mrs.' => 'Mrs.', 644 ); 645 646=cut 647 648sub GetFieldSelections { 649 my ( $Self, %Param ) = @_; 650 651 # check needed stuff 652 if ( !$Param{FieldName} ) { 653 $Kernel::OM->Get('Kernel::System::Log')->Log( 654 Priority => 'error', 655 Message => "Need FieldName!" 656 ); 657 return; 658 } 659 660 my %SelectionsData; 661 662 SOURCE: 663 for my $Count ( '', 1 .. 10 ) { 664 next SOURCE if !$Self->{"CustomerUser$Count"}; 665 next SOURCE if !$Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Selections}->{ $Param{FieldName} }; 666 667 %SelectionsData = ( 668 %SelectionsData, %{ $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Selections}->{ $Param{FieldName} } } 669 ); 670 } 671 672 # Make sure the encoding stamp is set. 673 for my $Key ( sort keys %SelectionsData ) { 674 $SelectionsData{$Key} = $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( $SelectionsData{$Key} ); 675 } 676 677 # Default handling for field 'ValidID'. 678 if ( !%SelectionsData && $Param{FieldName} =~ /^ValidID/i ) { 679 %SelectionsData = $Kernel::OM->Get('Kernel::System::Valid')->ValidList(); 680 } 681 682 return %SelectionsData; 683} 684 685=head2 CustomerIDList() 686 687return a list of with all known unique CustomerIDs of the registered customers users (no SearchTerm), 688or a filtered list where the CustomerIDs must contain a search term. 689 690 my @CustomerIDs = $CustomerUserObject->CustomerIDList( 691 SearchTerm => 'somecustomer', # optional 692 Valid => 1, # optional 693 ); 694 695=cut 696 697sub CustomerIDList { 698 my ( $Self, %Param ) = @_; 699 700 my @Data; 701 SOURCE: 702 for my $Count ( '', 1 .. 10 ) { 703 704 next SOURCE if !$Self->{"CustomerUser$Count"}; 705 706 # get customer list result of backend and merge it 707 push @Data, $Self->{"CustomerUser$Count"}->CustomerIDList(%Param); 708 } 709 710 # make entries unique 711 my %Tmp; 712 @Tmp{@Data} = undef; 713 @Data = sort { lc $a cmp lc $b } keys %Tmp; 714 715 return @Data; 716} 717 718=head2 CustomerName() 719 720get customer user name 721 722 my $Name = $CustomerUserObject->CustomerName( 723 UserLogin => 'some-login', 724 ); 725 726=cut 727 728sub CustomerName { 729 my ( $Self, %Param ) = @_; 730 731 SOURCE: 732 for my $Count ( '', 1 .. 10 ) { 733 734 next SOURCE if !$Self->{"CustomerUser$Count"}; 735 736 # Get customer name and return it. 737 my $Name = $Self->{"CustomerUser$Count"}->CustomerName(%Param); 738 if ($Name) { 739 return $Name; 740 } 741 } 742 return; 743} 744 745=head2 CustomerIDs() 746 747get customer user customer ids 748 749 my @CustomerIDs = $CustomerUserObject->CustomerIDs( 750 User => 'some-login', 751 ); 752 753=cut 754 755sub CustomerIDs { 756 my ( $Self, %Param ) = @_; 757 758 # check needed stuff 759 if ( !$Param{User} ) { 760 $Kernel::OM->Get('Kernel::System::Log')->Log( 761 Priority => 'error', 762 Message => 'Need User!' 763 ); 764 return; 765 } 766 767 # get customer ids (stop after first source with results) 768 my @CustomerIDs; 769 SOURCE: 770 for my $Count ( '', 1 .. 10 ) { 771 772 next SOURCE if !$Self->{"CustomerUser$Count"}; 773 774 # get customer ids from source 775 my @SourceCustomerIDs = $Self->{"CustomerUser$Count"}->CustomerIDs(%Param); 776 next SOURCE if !@SourceCustomerIDs; 777 778 @CustomerIDs = @SourceCustomerIDs; 779 last SOURCE; 780 } 781 782 # create hash with existing customer ids 783 my %CustomerIDs = map { $_ => 1 } @CustomerIDs; 784 785 # get related customer ids 786 my @RelatedCustomerIDs = $Self->CustomerUserCustomerMemberList( 787 CustomerUserID => $Param{User}, 788 ); 789 790 # add related customer ids if not found in source 791 RELATEDCUSTOMERID: 792 for my $RelatedCustomerID (@RelatedCustomerIDs) { 793 next RELATEDCUSTOMERID if $CustomerIDs{$RelatedCustomerID}; 794 795 push @CustomerIDs, $RelatedCustomerID; 796 } 797 798 # return customer ids 799 return @CustomerIDs; 800} 801 802=head2 CustomerUserDataGet() 803 804get user data (UserLogin, UserFirstname, UserLastname, UserEmail, ...) 805 806 my %User = $CustomerUserObject->CustomerUserDataGet( 807 User => 'franz', 808 ); 809 810=cut 811 812sub CustomerUserDataGet { 813 my ( $Self, %Param ) = @_; 814 815 return if !$Param{User}; 816 817 # fetch dynamic field configurations for CustomerUser. 818 my $DynamicFieldConfigs = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 819 ObjectType => 'CustomerUser', 820 Valid => 1, 821 ); 822 823 my %DynamicFieldLookup = map { $_->{Name} => $_ } @{$DynamicFieldConfigs}; 824 825 # Get needed objects. 826 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 827 my $CustomerCompanyObject = $Kernel::OM->Get('Kernel::System::CustomerCompany'); 828 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 829 830 SOURCE: 831 for my $Count ( '', 1 .. 10 ) { 832 833 next SOURCE if !$Self->{"CustomerUser$Count"}; 834 835 my %Customer = $Self->{"CustomerUser$Count"}->CustomerUserDataGet(%Param); 836 next SOURCE if !%Customer; 837 838 # generate the full name and save it in the hash 839 my $UserFullname = $Self->CustomerName(%Customer); 840 841 # save the generated fullname in the hash. 842 $Customer{UserFullname} = $UserFullname; 843 844 # add preferences defaults 845 my $Config = $ConfigObject->Get('CustomerPreferencesGroups'); 846 if ($Config) { 847 KEY: 848 for my $Key ( sort keys %{$Config} ) { 849 850 next KEY if !defined $Config->{$Key}->{DataSelected}; 851 next KEY if defined $Customer{ $Config->{$Key}->{PrefKey} }; 852 853 # set default data 854 $Customer{ $Config->{$Key}->{PrefKey} } = $Config->{$Key}->{DataSelected}; 855 } 856 } 857 858 # check if customer company support is enabled and get company data 859 my %Company; 860 if ( 861 $ConfigObject->Get("CustomerCompany") 862 && $ConfigObject->Get("CustomerUser$Count")->{CustomerCompanySupport} 863 ) 864 { 865 %Company = $CustomerCompanyObject->CustomerCompanyGet( 866 CustomerID => $Customer{UserCustomerID}, 867 ); 868 869 $Company{CustomerCompanyValidID} = $Company{ValidID}; 870 } 871 872 # fetch dynamic field values 873 if ( IsArrayRefWithData( $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map} ) ) { 874 CUSTOMERUSERFIELD: 875 for my $CustomerUserField ( @{ $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map} } ) { 876 next CUSTOMERUSERFIELD if $CustomerUserField->[5] ne 'dynamic_field'; 877 next CUSTOMERUSERFIELD if !$DynamicFieldLookup{ $CustomerUserField->[2] }; 878 879 my $Value = $DynamicFieldBackendObject->ValueGet( 880 DynamicFieldConfig => $DynamicFieldLookup{ $CustomerUserField->[2] }, 881 ObjectName => $Customer{UserID}, 882 ); 883 884 $Customer{ $CustomerUserField->[0] } = $Value; 885 } 886 } 887 888 # return customer data 889 return ( 890 %Company, 891 %Customer, 892 Source => "CustomerUser$Count", 893 Config => $ConfigObject->Get("CustomerUser$Count"), 894 CompanyConfig => $ConfigObject->Get( $Company{Source} // 'CustomerCompany' ), 895 ); 896 } 897 898 return; 899} 900 901=head2 CustomerUserAdd() 902 903to add new customer users 904 905 my $UserLogin = $CustomerUserObject->CustomerUserAdd( 906 Source => 'CustomerUser', # CustomerUser source config 907 UserFirstname => 'Huber', 908 UserLastname => 'Manfred', 909 UserCustomerID => 'A124', 910 UserLogin => 'mhuber', 911 UserPassword => 'some-pass', # not required 912 UserEmail => 'email@example.com', 913 ValidID => 1, 914 UserID => 123, 915 ); 916 917=cut 918 919sub CustomerUserAdd { 920 my ( $Self, %Param ) = @_; 921 922 # check data source 923 if ( !$Param{Source} ) { 924 $Param{Source} = 'CustomerUser'; 925 } 926 927 # check if user exists 928 if ( $Param{UserLogin} ) { 929 my %User = $Self->CustomerUserDataGet( User => $Param{UserLogin} ); 930 if (%User) { 931 $Kernel::OM->Get('Kernel::System::Log')->Log( 932 Priority => 'error', 933 Message => $Kernel::OM->Get('Kernel::Language') 934 ->Translate( 'Customer user "%s" already exists.', $Param{UserLogin} ), 935 ); 936 return; 937 } 938 } 939 940 # store customer user data 941 my $Result = $Self->{ $Param{Source} }->CustomerUserAdd(%Param); 942 return if !$Result; 943 944 # trigger event 945 $Self->EventHandler( 946 Event => 'CustomerUserAdd', 947 Data => { 948 UserLogin => $Param{UserLogin}, 949 NewData => \%Param, 950 }, 951 UserID => $Param{UserID}, 952 ); 953 954 return $Result; 955 956} 957 958=head2 CustomerUserUpdate() 959 960to update customer users 961 962 $CustomerUserObject->CustomerUserUpdate( 963 Source => 'CustomerUser', # CustomerUser source config 964 ID => 'mh' # current user login 965 UserLogin => 'mhuber', # new user login 966 UserFirstname => 'Huber', 967 UserLastname => 'Manfred', 968 UserPassword => 'some-pass', # not required 969 UserEmail => 'email@example.com', 970 ValidID => 1, 971 UserID => 123, 972 ); 973 974=cut 975 976sub CustomerUserUpdate { 977 my ( $Self, %Param ) = @_; 978 979 # check needed stuff 980 if ( !$Param{UserLogin} ) { 981 $Kernel::OM->Get('Kernel::System::Log')->Log( 982 Priority => 'error', 983 Message => "Need UserLogin!" 984 ); 985 return; 986 } 987 988 # check for UserLogin-renaming and if new UserLogin already exists... 989 if ( $Param{ID} && ( lc $Param{UserLogin} ne lc $Param{ID} ) ) { 990 my %User = $Self->CustomerUserDataGet( User => $Param{UserLogin} ); 991 if (%User) { 992 $Kernel::OM->Get('Kernel::System::Log')->Log( 993 Priority => 'error', 994 Message => $Kernel::OM->Get('Kernel::Language') 995 ->Translate( 'Customer user "%s" already exists.', $Param{UserLogin} ), 996 ); 997 return; 998 } 999 } 1000 1001 # check if user exists 1002 my %User = $Self->CustomerUserDataGet( User => $Param{ID} || $Param{UserLogin} ); 1003 if ( !%User ) { 1004 $Kernel::OM->Get('Kernel::System::Log')->Log( 1005 Priority => 'error', 1006 Message => "No such user '$Param{UserLogin}'!", 1007 ); 1008 return; 1009 } 1010 1011 my $Result = $Self->{ $User{Source} }->CustomerUserUpdate(%Param); 1012 return if !$Result; 1013 1014 # trigger event 1015 $Self->EventHandler( 1016 Event => 'CustomerUserUpdate', 1017 Data => { 1018 UserLogin => $Param{ID} || $Param{UserLogin}, 1019 NewData => \%Param, 1020 OldData => \%User, 1021 }, 1022 UserID => $Param{UserID}, 1023 ); 1024 1025 return $Result; 1026} 1027 1028=head2 SetPassword() 1029 1030to set customer users passwords 1031 1032 $CustomerUserObject->SetPassword( 1033 UserLogin => 'some-login', 1034 PW => 'some-new-password' 1035 ); 1036 1037=cut 1038 1039sub SetPassword { 1040 my ( $Self, %Param ) = @_; 1041 1042 # check needed stuff 1043 if ( !$Param{UserLogin} ) { 1044 $Kernel::OM->Get('Kernel::System::Log')->Log( 1045 Priority => 'error', 1046 Message => 'User UserLogin!' 1047 ); 1048 return; 1049 } 1050 1051 # check if user exists 1052 my %User = $Self->CustomerUserDataGet( User => $Param{UserLogin} ); 1053 if ( !%User ) { 1054 $Kernel::OM->Get('Kernel::System::Log')->Log( 1055 Priority => 'error', 1056 Message => "No such user '$Param{UserLogin}'!", 1057 ); 1058 return; 1059 } 1060 return $Self->{ $User{Source} }->SetPassword(%Param); 1061} 1062 1063=head2 GenerateRandomPassword() 1064 1065generate a random password 1066 1067 my $Password = $CustomerUserObject->GenerateRandomPassword(); 1068 1069 or 1070 1071 my $Password = $CustomerUserObject->GenerateRandomPassword( 1072 Size => 16, 1073 ); 1074 1075=cut 1076 1077sub GenerateRandomPassword { 1078 my ( $Self, %Param ) = @_; 1079 1080 return $Self->{CustomerUser}->GenerateRandomPassword(%Param); 1081} 1082 1083=head2 SetPreferences() 1084 1085set customer user preferences 1086 1087 $CustomerUserObject->SetPreferences( 1088 Key => 'UserComment', 1089 Value => 'some comment', 1090 UserID => 'some-login', 1091 ); 1092 1093=cut 1094 1095sub SetPreferences { 1096 my ( $Self, %Param ) = @_; 1097 1098 # check needed stuff 1099 if ( !$Param{UserID} ) { 1100 $Kernel::OM->Get('Kernel::System::Log')->Log( 1101 Priority => 'error', 1102 Message => 'Need UserID!' 1103 ); 1104 return; 1105 } 1106 1107 # Don't allow overwriting of native user data. 1108 my %Blacklisted = ( 1109 UserID => 1, 1110 UserLogin => 1, 1111 UserPassword => 1, 1112 UserFirstname => 1, 1113 UserLastname => 1, 1114 UserFullname => 1, 1115 UserStreet => 1, 1116 UserCity => 1, 1117 UserZip => 1, 1118 UserCountry => 1, 1119 UserComment => 1, 1120 UserCustomerID => 1, 1121 UserTitle => 1, 1122 UserEmail => 1, 1123 ChangeTime => 1, 1124 ChangeBy => 1, 1125 CreateTime => 1, 1126 CreateBy => 1, 1127 UserPhone => 1, 1128 UserMobile => 1, 1129 UserFax => 1, 1130 UserMailString => 1, 1131 ValidID => 1, 1132 ); 1133 1134 return 0 if $Blacklisted{ $Param{Key} }; 1135 1136 # check if user exists 1137 my %User = $Self->CustomerUserDataGet( User => $Param{UserID} ); 1138 if ( !%User ) { 1139 $Kernel::OM->Get('Kernel::System::Log')->Log( 1140 Priority => 'error', 1141 Message => "No such user '$Param{UserID}'!", 1142 ); 1143 return; 1144 } 1145 1146 # call new api (2.4.8 and higher) 1147 if ( $Self->{ $User{Source} }->can('SetPreferences') ) { 1148 return $Self->{ $User{Source} }->SetPreferences(%Param); 1149 } 1150 1151 # call old api 1152 return $Self->{PreferencesObject}->SetPreferences(%Param); 1153} 1154 1155=head2 GetPreferences() 1156 1157get customer user preferences 1158 1159 my %Preferences = $CustomerUserObject->GetPreferences( 1160 UserID => 'some-login', 1161 ); 1162 1163=cut 1164 1165sub GetPreferences { 1166 my ( $Self, %Param ) = @_; 1167 1168 # check needed stuff 1169 if ( !$Param{UserID} ) { 1170 $Kernel::OM->Get('Kernel::System::Log')->Log( 1171 Priority => 'error', 1172 Message => 'Need UserID!' 1173 ); 1174 return; 1175 } 1176 1177 # check if user exists 1178 my %User = $Self->CustomerUserDataGet( User => $Param{UserID} ); 1179 if ( !%User ) { 1180 $Kernel::OM->Get('Kernel::System::Log')->Log( 1181 Priority => 'error', 1182 Message => "No such user '$Param{UserID}'!", 1183 ); 1184 return; 1185 } 1186 1187 # call new api (2.4.8 and higher) 1188 if ( $Self->{ $User{Source} }->can('GetPreferences') ) { 1189 return $Self->{ $User{Source} }->GetPreferences(%Param); 1190 } 1191 1192 # call old api 1193 return $Self->{PreferencesObject}->GetPreferences(%Param); 1194} 1195 1196=head2 SearchPreferences() 1197 1198search in user preferences 1199 1200 my %UserList = $CustomerUserObject->SearchPreferences( 1201 Key => 'UserSomeKey', 1202 Value => 'SomeValue', # optional, limit to a certain value/pattern 1203 ); 1204 1205=cut 1206 1207sub SearchPreferences { 1208 my ( $Self, %Param ) = @_; 1209 1210 my %Data; 1211 SOURCE: 1212 for my $Count ( '', 1 .. 10 ) { 1213 1214 next SOURCE if !$Self->{"CustomerUser$Count"}; 1215 1216 # get customer search result of backend and merge it 1217 # call new api (2.4.8 and higher) 1218 my %SubData; 1219 if ( $Self->{"CustomerUser$Count"}->can('SearchPreferences') ) { 1220 %SubData = $Self->{"CustomerUser$Count"}->SearchPreferences(%Param); 1221 } 1222 1223 # call old api 1224 else { 1225 %SubData = $Self->{PreferencesObject}->SearchPreferences(%Param); 1226 } 1227 %Data = ( %SubData, %Data ); 1228 } 1229 1230 return %Data; 1231} 1232 1233=head2 TokenGenerate() 1234 1235generate a random token 1236 1237 my $Token = $CustomerUserObject->TokenGenerate( 1238 UserID => 123, 1239 ); 1240 1241=cut 1242 1243sub TokenGenerate { 1244 my ( $Self, %Param ) = @_; 1245 1246 # check needed stuff 1247 if ( !$Param{UserID} ) { 1248 $Kernel::OM->Get('Kernel::System::Log')->Log( 1249 Priority => 'error', 1250 Message => "Need UserID!" 1251 ); 1252 return; 1253 } 1254 1255 my $Token = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString( 1256 Length => 14, 1257 ); 1258 1259 # save token in preferences 1260 $Self->SetPreferences( 1261 Key => 'UserToken', 1262 Value => $Token, 1263 UserID => $Param{UserID}, 1264 ); 1265 1266 return $Token; 1267} 1268 1269=head2 TokenCheck() 1270 1271check password token 1272 1273 my $Valid = $CustomerUserObject>TokenCheck( 1274 Token => $Token, 1275 UserID => 123, 1276 ); 1277 1278=cut 1279 1280sub TokenCheck { 1281 my ( $Self, %Param ) = @_; 1282 1283 # check needed stuff 1284 if ( !$Param{Token} || !$Param{UserID} ) { 1285 $Kernel::OM->Get('Kernel::System::Log')->Log( 1286 Priority => 'error', 1287 Message => "Need Token and UserID!" 1288 ); 1289 return; 1290 } 1291 1292 # get preferences token 1293 my %Preferences = $Self->GetPreferences( 1294 UserID => $Param{UserID}, 1295 ); 1296 1297 # check requested vs. stored token 1298 return if !$Preferences{UserToken}; 1299 return if $Preferences{UserToken} ne $Param{Token}; 1300 1301 # reset password token 1302 $Self->SetPreferences( 1303 Key => 'UserToken', 1304 Value => '', 1305 UserID => $Param{UserID}, 1306 ); 1307 1308 return 1; 1309} 1310 1311=head2 CustomerUserCacheClear() 1312 1313clear cache of customer user data 1314 1315 $CustomerUserObject->CustomerUserCacheClear( 1316 UserLogin => 'mhuber', 1317 ); 1318 1319=cut 1320 1321sub CustomerUserCacheClear { 1322 my ( $Self, %Param ) = @_; 1323 1324 SOURCE: 1325 for my $Count ( '', 1 .. 10 ) { 1326 1327 next SOURCE if !$Self->{"CustomerUser$Count"}; 1328 $Self->{"CustomerUser$Count"}->_CustomerUserCacheClear( 1329 UserLogin => $Param{UserLogin}, 1330 ); 1331 } 1332 1333 return 1; 1334} 1335 1336=head2 CustomerUserCustomerMemberAdd() 1337 1338to add a customer user to a customer 1339 1340 my $Success = $CustomerUserObject->CustomerUserCustomerMemberAdd( 1341 CustomerUserID => 123, 1342 CustomerID => 123, 1343 Active => 1, # optional 1344 UserID => 123, 1345 ); 1346 1347=cut 1348 1349sub CustomerUserCustomerMemberAdd { 1350 my ( $Self, %Param ) = @_; 1351 1352 # check needed stuff 1353 for my $Argument (qw(CustomerUserID CustomerID UserID)) { 1354 if ( !$Param{$Argument} ) { 1355 $Kernel::OM->Get('Kernel::System::Log')->Log( 1356 Priority => 'error', 1357 Message => "Need $Argument!", 1358 ); 1359 return; 1360 } 1361 } 1362 1363 # delete affected caches 1364 my $CacheKey = 'Cache::CustomerUserCustomerMemberList::'; 1365 $Kernel::OM->Get('Kernel::System::Cache')->Delete( 1366 Type => $Self->{CacheType}, 1367 Key => $CacheKey . 'CustomerUserID::' . $Param{CustomerUserID}, 1368 ); 1369 $Kernel::OM->Get('Kernel::System::Cache')->Delete( 1370 Type => $Self->{CacheType}, 1371 Key => $CacheKey . 'CustomerID::' . $Param{CustomerID}, 1372 ); 1373 1374 # get database object 1375 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1376 1377 # delete existing relation 1378 return if !$DBObject->Do( 1379 SQL => 'DELETE FROM customer_user_customer 1380 WHERE user_id = ? 1381 AND customer_id = ?', 1382 Bind => [ \$Param{CustomerUserID}, \$Param{CustomerID} ], 1383 ); 1384 1385 # return if relation is not active 1386 return 1 if !$Param{Active}; 1387 1388 # insert new relation 1389 return if !$DBObject->Do( 1390 SQL => ' 1391 INSERT INTO customer_user_customer (user_id, customer_id, create_time, create_by, 1392 change_time, change_by) 1393 VALUES (?, ?, current_timestamp, ?, current_timestamp, ?)', 1394 Bind => [ \$Param{CustomerUserID}, \$Param{CustomerID}, \$Param{UserID}, \$Param{UserID}, ], 1395 ); 1396 1397 return 1; 1398} 1399 1400=head2 CustomerUserCustomerMemberList() 1401 1402get related customer IDs of a customer user 1403 1404 my @CustomerIDs = $CustomerUserObject->CustomerUserCustomerMemberList( 1405 CustomerUserID => 123, 1406 ); 1407 1408Returns: 1409 @CustomerIDs = ( 1410 '123', 1411 '456', 1412 ); 1413 1414get related customer users of a customer ID 1415 1416 my @CustomerUsers = $CustomerUserObject->CustomerUserCustomerMemberList( 1417 CustomerID => 123, 1418 ); 1419 1420Returns: 1421 @CustomerUsers = ( 1422 '123', 1423 '456', 1424 ); 1425 1426=cut 1427 1428sub CustomerUserCustomerMemberList { 1429 my ( $Self, %Param ) = @_; 1430 1431 # check needed stuff 1432 if ( !$Param{CustomerUserID} && !$Param{CustomerID} ) { 1433 $Kernel::OM->Get('Kernel::System::Log')->Log( 1434 Priority => 'error', 1435 Message => 'Got no CustomerUserID or CustomerID!', 1436 ); 1437 return; 1438 } 1439 1440 # get needed objects 1441 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 1442 my $CacheKey = 'Cache::CustomerUserCustomerMemberList::'; 1443 1444 if ( $Param{CustomerUserID} ) { 1445 1446 # check if this result is present (in cache) 1447 $CacheKey .= 'CustomerUserID::' . $Param{CustomerUserID}; 1448 my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1449 Type => $Self->{CacheType}, 1450 Key => $CacheKey, 1451 ); 1452 return @{$Cache} if $Cache; 1453 1454 # get customer ids 1455 return if !$DBObject->Prepare( 1456 SQL => 1457 'SELECT customer_id 1458 FROM customer_user_customer 1459 WHERE user_id = ? 1460 ORDER BY customer_id', 1461 Bind => [ \$Param{CustomerUserID}, ], 1462 ); 1463 1464 # fetch the result 1465 my @CustomerIDs; 1466 while ( my @Row = $DBObject->FetchrowArray() ) { 1467 push @CustomerIDs, $Row[0]; 1468 } 1469 1470 # cache the result 1471 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1472 Type => $Self->{CacheType}, 1473 TTL => $Self->{CacheTTL}, 1474 Key => $CacheKey, 1475 Value => \@CustomerIDs, 1476 1477 ); 1478 1479 return @CustomerIDs; 1480 } 1481 else { 1482 1483 # check if this result is present (in cache) 1484 $CacheKey .= 'CustomerID::' . $Param{CustomerID}; 1485 my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( 1486 Type => $Self->{CacheType}, 1487 Key => $CacheKey, 1488 ); 1489 return @{$Cache} if $Cache; 1490 1491 # get customer users 1492 return if !$DBObject->Prepare( 1493 SQL => 1494 'SELECT user_id 1495 FROM customer_user_customer WHERE 1496 customer_id = ? 1497 ORDER BY user_id', 1498 Bind => [ \$Param{CustomerID}, ], 1499 ); 1500 1501 # fetch the result 1502 my @CustomerUserIDs; 1503 while ( my @Row = $DBObject->FetchrowArray() ) { 1504 push @CustomerUserIDs, $Row[0]; 1505 } 1506 1507 # cache the result 1508 $Kernel::OM->Get('Kernel::System::Cache')->Set( 1509 Type => $Self->{CacheType}, 1510 TTL => $Self->{CacheTTL}, 1511 Key => $CacheKey, 1512 Value => \@CustomerUserIDs, 1513 ); 1514 1515 return @CustomerUserIDs; 1516 } 1517} 1518 1519sub DESTROY { 1520 my $Self = shift; 1521 1522 # execute all transaction events 1523 $Self->EventHandlerTransaction(); 1524 1525 return 1; 1526} 1527 15281; 1529 1530=head1 TERMS AND CONDITIONS 1531 1532This software is part of the OTRS project (L<https://otrs.org/>). 1533 1534This software comes with ABSOLUTELY NO WARRANTY. For details, see 1535the enclosed file COPYING for license information (GPL). If you 1536did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 1537 1538=cut 1539