# -- # OTOBO is a web-based ticketing system for service organisations. # -- # Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ # Copyright (C) 2019-2023 Rother OSS GmbH, https://otobo.de/ # -- # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -- package Kernel::System::CustomerUser; use strict; use warnings; use parent qw(Kernel::System::EventHandler); use Kernel::System::VariableCheck qw(:all); our @ObjectDependencies = ( 'Kernel::Config', 'Kernel::Language', 'Kernel::System::Cache', 'Kernel::System::CustomerCompany', 'Kernel::System::DB', 'Kernel::System::DynamicField', 'Kernel::System::DynamicField::Backend', 'Kernel::System::Encode', 'Kernel::System::Log', 'Kernel::System::Main', 'Kernel::System::Valid', ); =head1 NAME Kernel::System::CustomerUser - customer user lib =head1 DESCRIPTION All customer user functions. E. g. to add and update customer users. =head1 PUBLIC INTERFACE =head2 new() Don't use the constructor directly, use the ObjectManager instead: my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); =cut sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {}; bless( $Self, $Type ); $Self->{CacheType} = 'CustomerUser'; $Self->{CacheTTL} = 60 * 60 * 24 * 20; # get config object my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); # load generator customer preferences module my $GeneratorModule = $ConfigObject->Get('CustomerPreferences')->{Module} || 'Kernel::System::CustomerUser::Preferences::DB'; # get main object my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); if ( $MainObject->Require($GeneratorModule) ) { $Self->{PreferencesObject} = $GeneratorModule->new(); } # load customer user backend module SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$ConfigObject->Get("CustomerUser$Count"); my $GenericModule = $ConfigObject->Get("CustomerUser$Count")->{Module}; if ( !$MainObject->Require($GenericModule) ) { $MainObject->Die("Can't load backend module $GenericModule! $@"); } $Self->{"CustomerUser$Count"} = $GenericModule->new( Count => $Count, PreferencesObject => $Self->{PreferencesObject}, CustomerUserMap => $ConfigObject->Get("CustomerUser$Count"), ); } # init of event handler $Self->EventHandlerInit( Config => 'CustomerUser::EventModulePost', ); return $Self; } =head2 CustomerSourceList() return customer source list my %List = $CustomerUserObject->CustomerSourceList( ReadOnly => 0 # optional, 1 returns only RO backends, 0 returns writable, if not passed returns all backends ); =cut sub CustomerSourceList { my ( $Self, %Param ) = @_; # get config object my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my %Data; SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$ConfigObject->Get("CustomerUser$Count"); if ( defined $Param{ReadOnly} ) { my $CustomerBackendConfig = $ConfigObject->Get("CustomerUser$Count"); if ( $Param{ReadOnly} ) { next SOURCE if !$CustomerBackendConfig->{ReadOnly} || $CustomerBackendConfig->{Module} !~ /LDAP/i; } else { next SOURCE if $CustomerBackendConfig->{ReadOnly} || $CustomerBackendConfig->{Module} =~ /LDAP/i; } } $Data{"CustomerUser$Count"} = $ConfigObject->Get("CustomerUser$Count")->{Name} || "No Name $Count"; } return %Data; } =head2 CustomerSearch() to search users # text search my %List = $CustomerUserObject->CustomerSearch( Search => '*some*', # also 'hans+huber' possible Valid => 1, # (optional) default 1 Limit => 100, # (optional) overrides limit of the config # RotherOSS / ConsoleCommands Source => ['ldap'], # (optional) limit search to certain backends # EO ConsoleCommands ); # username search my %List = $CustomerUserObject->CustomerSearch( UserLogin => '*some*', Valid => 1, # (optional) default 1 ); # email search my %List = $CustomerUserObject->CustomerSearch( PostMasterSearch => 'email@example.com', Valid => 1, # (optional) default 1 ); # search by CustomerID my %List = $CustomerUserObject->CustomerSearch( CustomerID => 'CustomerID123', Valid => 1, # (optional) default 1 ); =cut sub CustomerSearch { my ( $Self, %Param ) = @_; # RotherOSS / ConsoleCommands if ( $Param{Source} && !IsArrayRefWithData( $Param{Source} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'debug', Message => "Source filter is empty, ignoring it.", ); } # EO ConsoleCommands # remove leading and ending spaces if ( $Param{Search} ) { $Param{Search} =~ s/^\s+//; $Param{Search} =~ s/\s+$//; } # Get dynamic field object. my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); my $DynamicFieldConfigs = $DynamicFieldObject->DynamicFieldListGet( ObjectType => 'CustomerUser', Valid => 1, ); my %DynamicFieldLookup = map { $_->{Name} => $_ } @{$DynamicFieldConfigs}; # Get dynamic field backend object. my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); my %Data; SOURCE: for my $Count ( '', 1 .. 10 ) { # RotherOSS / ConsoleCommands next SOURCE if $Param{Source} && !grep { $_ eq "CustomerUser$Count" } $Param{Source}->@*; # EO ConsoleCommands next SOURCE if !$Self->{"CustomerUser$Count"}; # search dynamic field values, if configured my $Map = $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map}; if ( IsArrayRefWithData($Map) ) { # fetch dynamic field names that are configured in Map # only these will be considered for any other search config # [ 'DynamicField_Name_X', undef, 'Name_X', 0, 0, 'dynamic_field', undef, 0, undef, undef, ], my %DynamicFieldNames = map { $_->[2] => 1 } grep { $_->[5] eq 'dynamic_field' } @{$Map}; if ( IsHashRefWithData( \%DynamicFieldNames ) ) { my $FoundDynamicFieldObjectIDs; my $SearchFields; my $SearchParam; # check which of the dynamic fields configured in Map are also # configured in SearchFields # param Search if ( defined $Param{Search} && length $Param{Search} ) { $SearchFields = $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{CustomerUserSearchFields}; $SearchParam = $Param{Search}; } # param PostMasterSearch elsif ( defined $Param{PostMasterSearch} && length $Param{PostMasterSearch} ) { $SearchFields = $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{CustomerUserPostMasterSearchFields}; $SearchParam = $Param{PostMasterSearch}; } # search dynamic field values if ( IsArrayRefWithData($SearchFields) ) { my @SearchDynamicFieldNames = grep { exists $DynamicFieldNames{$_} } @{$SearchFields}; my %FoundDynamicFieldObjectIDs; FIELDNAME: for my $FieldName (@SearchDynamicFieldNames) { my $DynamicFieldConfig = $DynamicFieldLookup{$FieldName}; next FIELDNAME if !IsHashRefWithData($DynamicFieldConfig); my $DynamicFieldValues = $DynamicFieldBackendObject->ValueSearch( DynamicFieldConfig => $DynamicFieldConfig, Search => $SearchParam, ); if ( IsArrayRefWithData($DynamicFieldValues) ) { for my $DynamicFieldValue ( @{$DynamicFieldValues} ) { $FoundDynamicFieldObjectIDs{ $DynamicFieldValue->{ObjectID} } = 1; } } } $FoundDynamicFieldObjectIDs = [ keys %FoundDynamicFieldObjectIDs ]; } # execute backend search for found object IDs # this data is being merged with the following CustomerSearch call if ( IsArrayRefWithData($FoundDynamicFieldObjectIDs) ) { my $ObjectNames = $DynamicFieldObject->ObjectMappingGet( ObjectID => $FoundDynamicFieldObjectIDs, ObjectType => 'CustomerUser', ); OBJECTNAME: for my $ObjectName ( values %{$ObjectNames} ) { next OBJECTNAME if exists $Data{$ObjectName}; my %SearchParam = %Param; delete $SearchParam{Search}; delete $SearchParam{PostMasterSearch}; $SearchParam{UserLogin} = $ObjectName; my %SubData = $Self->{"CustomerUser$Count"}->CustomerSearch(%SearchParam); # UserLogin search does a wild-card search, but in this case only the # exact matching user login is relevant if ( IsHashRefWithData( \%SubData ) && exists $SubData{$ObjectName} ) { %Data = ( $ObjectName => $SubData{$ObjectName}, %Data ); } } } } } # get customer search result of backend and merge it my %SubData = $Self->{"CustomerUser$Count"}->CustomerSearch(%Param); %Data = ( %SubData, %Data ); } return %Data; } =head2 CustomerSearchDetail() To find customer users in the system. The search criteria are logically AND connected. When a list is passed as criteria, the individual members are OR connected. When an undef or a reference to an empty array is passed, then the search criteria is ignored. Returns either a list, as an arrayref, or a count of found customer user ids. The count of results is returned when the parameter C is passed. my $CustomerUserIDsRef = $CustomerUserObject->CustomerSearchDetail( # all fields which are defined in a CustomerUserMap, except password fields, are searchable UserLogin => 'example*', # (optional) UserFirstname => 'Firstn*', # (optional) # special parameters CustomerCompanySearchCustomerIDs => [ 'example.com' ], # (optional) ExcludeUserLogins => [ 'example', 'doejohn' ], # (optional) # array parameters are used with logical OR operator (all values are possible which are defined in the config selection hash for the field) UserCountry => [ 'Austria', 'Germany', ], # (optional) # DynamicFields # At least one operator must be specified. Operators will be connected with AND, # values in an operator with OR. # You can also pass more than one argument to an operator: ['value1', 'value2'] DynamicField_FieldNameX => { Equals => 123, Like => 'value*', # "equals" operator with wildcard support GreaterThan => '2001-01-01 01:01:01', GreaterThanEquals => '2001-01-01 01:01:01', SmallerThan => '2002-02-02 02:02:02', SmallerThanEquals => '2002-02-02 02:02:02', } OrderBy => [ 'UserLogin', 'UserCustomerID' ], # (optional) # ignored if the result type is 'COUNT' # default: [ 'UserLogin' ] # (all fields which are defined in a CustomerUserMap can be used for ordering) # Additional information for OrderBy: # The OrderByDirection can be specified for each OrderBy attribute. # The pairing is made by the array indices. OrderByDirection => [ 'Down', 'Up' ], # (optional) # ignored if the result type is 'COUNT' # (Down | Up) Default: [ 'Down' ] Result => 'ARRAY' || 'COUNT', # (optional) # default: ARRAY, returns an array of change ids # COUNT returns a scalar with the number of found changes Limit => 100, # (optional) # ignored if the result type is 'COUNT' ); Returns: Result: 'ARRAY' @CustomerUserIDs = ( 1, 2, 3 ); Result: 'COUNT' $CustomerUserIDs = 10; =cut sub CustomerSearchDetail { my ( $Self, %Param ) = @_; # get all general search fields (without a restriction to a source) my @AllSearchFields = $Self->CustomerUserSearchFields(); # generate a hash with the customer user sources which must be searched my %SearchCustomerUserSources; SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; # get the search fields for the current source my @SourceSearchFields = $Self->CustomerUserSearchFields( Source => "CustomerUser$Count", ); my %LookupSourceSearchFields = map { $_->{Name} => 1 } @SourceSearchFields; # check if all search param exists in the search fields from the current source SEARCHFIELD: for my $SearchField (@AllSearchFields) { next SEARCHFIELD if !$Param{ $SearchField->{Name} }; next SOURCE if !$LookupSourceSearchFields{ $SearchField->{Name} }; } $SearchCustomerUserSources{"CustomerUser$Count"} = \@SourceSearchFields; } # set the default behaviour for the return type $Param{Result} ||= 'ARRAY'; if ( $Param{Result} eq 'COUNT' ) { my $IDsCount = 0; SOURCE: for my $Source ( sort keys %SearchCustomerUserSources ) { next SOURCE if !$Self->{$Source}; my $SubIDsCount = $Self->{$Source}->CustomerSearchDetail( %Param, SearchFields => $SearchCustomerUserSources{$Source}, ); return if !defined $SubIDsCount; $IDsCount += $SubIDsCount || 0; } return $IDsCount; } else { my @IDs; my $ResultCount = 0; SOURCE: for my $Source ( sort keys %SearchCustomerUserSources ) { next SOURCE if !$Self->{$Source}; my $SubIDs = $Self->{$Source}->CustomerSearchDetail( %Param, SearchFields => $SearchCustomerUserSources{$Source}, ); return if !defined $SubIDs; next SOURCE if !IsArrayRefWithData($SubIDs); push @IDs, @{$SubIDs}; $ResultCount++; } # if we have more then one search results from diffrent sources, we need a resorting # because of the merged single results if ( $ResultCount > 1 ) { my @UserDataList; for my $ID (@IDs) { my %UserData = $Self->CustomerUserDataGet( User => $ID, ); push @UserDataList, \%UserData; } my $OrderBy = 'UserLogin'; if ( IsArrayRefWithData( $Param{OrderBy} ) ) { $OrderBy = $Param{OrderBy}->[0]; } if ( IsArrayRefWithData( $Param{OrderByDirection} ) && $Param{OrderByDirection}->[0] eq 'Up' ) { @UserDataList = sort { lc( $a->{$OrderBy} ) cmp lc( $b->{$OrderBy} ) } @UserDataList; } else { @UserDataList = sort { lc( $b->{$OrderBy} ) cmp lc( $a->{$OrderBy} ) } @UserDataList; } if ( $Param{Limit} && scalar @UserDataList > $Param{Limit} ) { splice @UserDataList, $Param{Limit}; } @IDs = map { $_->{UserLogin} } @UserDataList; } return \@IDs; } } =head2 CustomerUserSearchFields() Get a list of the defined search fields (optional only the relevant fields for the given source). my @SeachFields = $CustomerUserObject->CustomerUserSearchFields( Source => 'CustomerUser', # optional, but important in the CustomerSearchDetail to get the right database fields ); Returns an array of hash references. @SeachFields = ( { Name => 'UserEmail', Label => 'Email', Type => 'Input', DatabaseField => 'mail', }, { Name => 'UserCountry', Label => 'Country', Type => 'Selection', SelectionsData => { 'Germany' => 'Germany', 'United Kingdom' => 'United Kingdom', 'United States' => 'United States', ... }, DatabaseField => 'country', }, { Name => 'DynamicField_SkypeAccountName', Label => '', Type => 'DynamicField', DatabaseField => 'SkypeAccountName', }, ); =cut sub CustomerUserSearchFields { my ( $Self, %Param ) = @_; # Get the search fields from all customer user maps (merge from all maps together). my @SearchFields; my %SearchFieldsExists; SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; next SOURCE if $Param{Source} && $Param{Source} ne "CustomerUser$Count"; ENTRY: for my $Entry ( @{ $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map} } ) { my $SearchFieldName = $Entry->[0]; next ENTRY if $SearchFieldsExists{$SearchFieldName}; next ENTRY if $SearchFieldName =~ m{(Password|Pw)\d*$}smxi; # Remember the already collected search field name. $SearchFieldsExists{$SearchFieldName} = 1; my %FieldConfig = $Self->GetFieldConfig( FieldName => $SearchFieldName, Source => $Param{Source}, # to get the right database field for the given source ); next ENTRY if !%FieldConfig; my %SearchFieldData = ( %FieldConfig, Name => $SearchFieldName, ); my %SelectionsData = $Self->GetFieldSelections( FieldName => $SearchFieldName, ); if ( $SearchFieldData{StorageType} eq 'dynamic_field' ) { $SearchFieldData{Type} = 'DynamicField'; } elsif (%SelectionsData) { $SearchFieldData{Type} = 'Selection'; $SearchFieldData{SelectionsData} = \%SelectionsData; } else { $SearchFieldData{Type} = 'Input'; } push @SearchFields, \%SearchFieldData; } } return @SearchFields; } =head2 GetFieldConfig() This function collect some field config information from the customer user map. my %FieldConfig = $CustomerUserObject->GetFieldConfig( FieldName => 'UserEmail', Source => 'CustomerUser', # optional ); Returns some field config information: my %FieldConfig = ( Label => 'Email', DatabaseField => 'email', StorageType => 'var', ); =cut sub GetFieldConfig { my ( $Self, %Param ) = @_; if ( !$Param{FieldName} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need FieldName!" ); return; } SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; next SOURCE if $Param{Source} && $Param{Source} ne "CustomerUser$Count"; # Search the right field and return some config information from the field. ENTRY: for my $Entry ( @{ $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map} } ) { next ENTRY if $Param{FieldName} ne $Entry->[0]; my %FieldConfig = ( Label => $Entry->[1], DatabaseField => $Entry->[2], StorageType => $Entry->[5], ); return %FieldConfig; } } return; } =head2 GetFieldSelections() This function collect the selections for the given field name, if the field has some selections. my %SelectionsData = $CustomerUserObject->GetFieldSelections( FieldName => 'UserTitle', ); Returns the selections for the given field name (merged from all sources) or a empty hash: my %SelectionData = ( 'Mr.' => 'Mr.', 'Mrs.' => 'Mrs.', ); =cut sub GetFieldSelections { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{FieldName} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need FieldName!" ); return; } my %SelectionsData; SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; next SOURCE if !$Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Selections}->{ $Param{FieldName} }; %SelectionsData = ( %SelectionsData, %{ $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Selections}->{ $Param{FieldName} } } ); } # Make sure the encoding stamp is set. for my $Key ( sort keys %SelectionsData ) { $SelectionsData{$Key} = $Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( $SelectionsData{$Key} ); } # Default handling for field 'ValidID'. if ( !%SelectionsData && $Param{FieldName} =~ /^ValidID/i ) { %SelectionsData = $Kernel::OM->Get('Kernel::System::Valid')->ValidList(); } return %SelectionsData; } =head2 CustomerIDList() return a list of with all known unique CustomerIDs of the registered customers users (no SearchTerm), or a filtered list where the CustomerIDs must contain a search term. my @CustomerIDs = $CustomerUserObject->CustomerIDList( SearchTerm => 'somecustomer', # optional Valid => 1, # optional ); =cut sub CustomerIDList { my ( $Self, %Param ) = @_; my @Data; SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; # get customer list result of backend and merge it push @Data, $Self->{"CustomerUser$Count"}->CustomerIDList(%Param); } # make entries unique my %Tmp; @Tmp{@Data} = undef; @Data = sort { lc $a cmp lc $b } keys %Tmp; return @Data; } =head2 CustomerName() get customer user name my $Name = $CustomerUserObject->CustomerName( UserLogin => 'some-login', ); =cut sub CustomerName { my ( $Self, %Param ) = @_; SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; # Get customer name and return it. my $Name = $Self->{"CustomerUser$Count"}->CustomerName(%Param); if ($Name) { return $Name; } } return; } =head2 CustomerIDs() get customer user customer ids my @CustomerIDs = $CustomerUserObject->CustomerIDs( User => 'some-login', ); =cut sub CustomerIDs { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{User} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need User!' ); return; } # get customer ids (stop after first source with results) my @CustomerIDs; SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; # get customer ids from source my @SourceCustomerIDs = $Self->{"CustomerUser$Count"}->CustomerIDs(%Param); next SOURCE if !@SourceCustomerIDs; @CustomerIDs = @SourceCustomerIDs; last SOURCE; } # create hash with existing customer ids my %CustomerIDs = map { $_ => 1 } @CustomerIDs; # get related customer ids my @RelatedCustomerIDs = $Self->CustomerUserCustomerMemberList( CustomerUserID => $Param{User}, ); # add related customer ids if not found in source RELATEDCUSTOMERID: for my $RelatedCustomerID (@RelatedCustomerIDs) { next RELATEDCUSTOMERID if $CustomerIDs{$RelatedCustomerID}; push @CustomerIDs, $RelatedCustomerID; } # return customer ids return @CustomerIDs; } =head2 CustomerUserDataGet() get user data (UserLogin, UserFirstname, UserLastname, UserEmail, ...) my %User = $CustomerUserObject->CustomerUserDataGet( User => 'franz', ); =cut sub CustomerUserDataGet { my ( $Self, %Param ) = @_; return if !$Param{User}; # fetch dynamic field configurations for CustomerUser. my $DynamicFieldConfigs = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( ObjectType => 'CustomerUser', Valid => 1, ); my %DynamicFieldLookup = map { $_->{Name} => $_ } @{$DynamicFieldConfigs}; # Get needed objects. my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my $CustomerCompanyObject = $Kernel::OM->Get('Kernel::System::CustomerCompany'); my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; my %Customer = $Self->{"CustomerUser$Count"}->CustomerUserDataGet(%Param); next SOURCE if !%Customer; # generate the full name and save it in the hash my $UserFullname = $Self->CustomerName(%Customer); # save the generated fullname in the hash. $Customer{UserFullname} = $UserFullname; # add preferences defaults my $Config = $ConfigObject->Get('CustomerPreferencesGroups'); if ($Config) { KEY: for my $Key ( sort keys %{$Config} ) { next KEY if !defined $Config->{$Key}->{DataSelected}; next KEY if defined $Customer{ $Config->{$Key}->{PrefKey} }; # set default data $Customer{ $Config->{$Key}->{PrefKey} } = $Config->{$Key}->{DataSelected}; } } # check if customer company support is enabled and get company data my %Company; if ( $ConfigObject->Get("CustomerCompany") && $ConfigObject->Get("CustomerUser$Count")->{CustomerCompanySupport} ) { %Company = $CustomerCompanyObject->CustomerCompanyGet( CustomerID => $Customer{UserCustomerID}, ); $Company{CustomerCompanyValidID} = $Company{ValidID}; } # fetch dynamic field values if ( IsArrayRefWithData( $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map} ) ) { CUSTOMERUSERFIELD: for my $CustomerUserField ( @{ $Self->{"CustomerUser$Count"}->{CustomerUserMap}->{Map} } ) { next CUSTOMERUSERFIELD if $CustomerUserField->[5] ne 'dynamic_field'; next CUSTOMERUSERFIELD if !$DynamicFieldLookup{ $CustomerUserField->[2] }; my $Value = $DynamicFieldBackendObject->ValueGet( DynamicFieldConfig => $DynamicFieldLookup{ $CustomerUserField->[2] }, ObjectName => $Customer{UserID}, ); $Customer{ $CustomerUserField->[0] } = $Value; } } # return customer data return ( %Company, %Customer, Source => "CustomerUser$Count", Config => $ConfigObject->Get("CustomerUser$Count"), CompanyConfig => $ConfigObject->Get( $Company{Source} // 'CustomerCompany' ), ); } return; } =head2 CustomerUserAdd() to add new customer users my $UserLogin = $CustomerUserObject->CustomerUserAdd( Source => 'CustomerUser', # CustomerUser source config UserFirstname => 'Huber', UserLastname => 'Manfred', UserCustomerID => 'A124', UserLogin => 'mhuber', UserPassword => 'some-pass', # not required UserEmail => 'email@example.com', ValidID => 1, UserID => 123, ); =cut sub CustomerUserAdd { my ( $Self, %Param ) = @_; # check data source if ( !$Param{Source} ) { $Param{Source} = 'CustomerUser'; } # check if user exists if ( $Param{UserLogin} ) { my %User = $Self->CustomerUserDataGet( User => $Param{UserLogin} ); if (%User) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => $Kernel::OM->Get('Kernel::Language')->Translate( 'Customer user "%s" already exists.', $Param{UserLogin} ), ); return; } } # store customer user data my $Result = $Self->{ $Param{Source} }->CustomerUserAdd(%Param); return if !$Result; # trigger event $Self->EventHandler( Event => 'CustomerUserAdd', Data => { UserLogin => $Param{UserLogin}, NewData => \%Param, }, UserID => $Param{UserID}, ); return $Result; } =head2 CustomerUserUpdate() to update customer users $CustomerUserObject->CustomerUserUpdate( Source => 'CustomerUser', # CustomerUser source config ID => 'mh' # current user login UserLogin => 'mhuber', # new user login UserFirstname => 'Huber', UserLastname => 'Manfred', UserPassword => 'some-pass', # not required UserEmail => 'email@example.com', ValidID => 1, UserID => 123, ); =cut sub CustomerUserUpdate { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserLogin} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need UserLogin!" ); return; } # check for UserLogin-renaming and if new UserLogin already exists... if ( $Param{ID} && ( lc $Param{UserLogin} ne lc $Param{ID} ) ) { my %User = $Self->CustomerUserDataGet( User => $Param{UserLogin} ); if (%User) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => $Kernel::OM->Get('Kernel::Language')->Translate( 'Customer user "%s" already exists.', $Param{UserLogin} ), ); return; } } # check if user exists my %User = $Self->CustomerUserDataGet( User => $Param{ID} || $Param{UserLogin} ); if ( !%User ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "No such user '$Param{UserLogin}'!", ); return; } my $Result = $Self->{ $User{Source} }->CustomerUserUpdate(%Param); return if !$Result; # trigger event $Self->EventHandler( Event => 'CustomerUserUpdate', Data => { UserLogin => $Param{ID} || $Param{UserLogin}, NewData => \%Param, OldData => \%User, }, UserID => $Param{UserID}, ); return $Result; } =head2 SetPassword() to set customer users passwords $CustomerUserObject->SetPassword( UserLogin => 'some-login', PW => 'some-new-password' ); =cut sub SetPassword { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserLogin} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'User UserLogin!' ); return; } # check if user exists my %User = $Self->CustomerUserDataGet( User => $Param{UserLogin} ); if ( !%User ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "No such user '$Param{UserLogin}'!", ); return; } return $Self->{ $User{Source} }->SetPassword(%Param); } =head2 GenerateRandomPassword() generate a random password my $Password = $CustomerUserObject->GenerateRandomPassword(); or my $Password = $CustomerUserObject->GenerateRandomPassword( Size => 16, ); =cut sub GenerateRandomPassword { my ( $Self, %Param ) = @_; return $Self->{CustomerUser}->GenerateRandomPassword(%Param); } =head2 SetPreferences() set customer user preferences $CustomerUserObject->SetPreferences( Key => 'UserComment', Value => 'some comment', UserID => 'some-login', ); =cut sub SetPreferences { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need UserID!' ); return; } # Don't allow overwriting of native user data. my %Blacklisted = ( UserID => 1, UserLogin => 1, UserPassword => 1, UserFirstname => 1, UserLastname => 1, UserFullname => 1, UserStreet => 1, UserCity => 1, UserZip => 1, UserCountry => 1, UserComment => 1, UserCustomerID => 1, UserTitle => 1, UserEmail => 1, ChangeTime => 1, ChangeBy => 1, CreateTime => 1, CreateBy => 1, UserPhone => 1, UserMobile => 1, UserFax => 1, UserMailString => 1, ValidID => 1, ); return 0 if $Blacklisted{ $Param{Key} }; # check if user exists my %User = $Self->CustomerUserDataGet( User => $Param{UserID} ); if ( !%User ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "No such user '$Param{UserID}'!", ); return; } # call new api (2.4.8 and higher) if ( $Self->{ $User{Source} }->can('SetPreferences') ) { return $Self->{ $User{Source} }->SetPreferences(%Param); } # call old api return $Self->{PreferencesObject}->SetPreferences(%Param); } =head2 GetPreferences() get customer user preferences my %Preferences = $CustomerUserObject->GetPreferences( UserID => 'some-login', ); =cut sub GetPreferences { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need UserID!' ); return; } # check if user exists my %User = $Self->CustomerUserDataGet( User => $Param{UserID} ); if ( !%User ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "No such user '$Param{UserID}'!", ); return; } # call new api (2.4.8 and higher) if ( $Self->{ $User{Source} }->can('GetPreferences') ) { return $Self->{ $User{Source} }->GetPreferences(%Param); } # call old api return $Self->{PreferencesObject}->GetPreferences(%Param); } =head2 SearchPreferences() search in user preferences my %UserList = $CustomerUserObject->SearchPreferences( Key => 'UserSomeKey', Value => 'SomeValue', # optional, limit to a certain value/pattern ); =cut sub SearchPreferences { my ( $Self, %Param ) = @_; my %Data; SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; # get customer search result of backend and merge it # call new api (2.4.8 and higher) my %SubData; if ( $Self->{"CustomerUser$Count"}->can('SearchPreferences') ) { %SubData = $Self->{"CustomerUser$Count"}->SearchPreferences(%Param); } # call old api else { %SubData = $Self->{PreferencesObject}->SearchPreferences(%Param); } %Data = ( %SubData, %Data ); } return %Data; } =head2 TokenGenerate() generate a random token my $Token = $CustomerUserObject->TokenGenerate( UserID => 123, ); =cut sub TokenGenerate { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need UserID!" ); return; } my $Token = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString( Length => 14, ); # save token in preferences $Self->SetPreferences( Key => 'UserToken', Value => $Token, UserID => $Param{UserID}, ); return $Token; } =head2 TokenCheck() check password token my $Valid = $CustomerUserObject>TokenCheck( Token => $Token, UserID => 123, ); =cut sub TokenCheck { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{Token} || !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need Token and UserID!" ); return; } # get preferences token my %Preferences = $Self->GetPreferences( UserID => $Param{UserID}, ); # check requested vs. stored token return if !$Preferences{UserToken}; return if $Preferences{UserToken} ne $Param{Token}; # reset password token $Self->SetPreferences( Key => 'UserToken', Value => '', UserID => $Param{UserID}, ); return 1; } =head2 CustomerUserCacheClear() clear cache of customer user data $CustomerUserObject->CustomerUserCacheClear( UserLogin => 'mhuber', ); =cut sub CustomerUserCacheClear { my ( $Self, %Param ) = @_; SOURCE: for my $Count ( '', 1 .. 10 ) { next SOURCE if !$Self->{"CustomerUser$Count"}; $Self->{"CustomerUser$Count"}->_CustomerUserCacheClear( UserLogin => $Param{UserLogin}, ); } return 1; } =head2 CustomerUserCustomerMemberAdd() to add a customer user to a customer my $Success = $CustomerUserObject->CustomerUserCustomerMemberAdd( CustomerUserID => 123, CustomerID => 123, Active => 1, # optional UserID => 123, ); =cut sub CustomerUserCustomerMemberAdd { my ( $Self, %Param ) = @_; # check needed stuff for my $Argument (qw(CustomerUserID CustomerID UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # delete affected caches my $CacheKey = 'Cache::CustomerUserCustomerMemberList::'; $Kernel::OM->Get('Kernel::System::Cache')->Delete( Type => $Self->{CacheType}, Key => $CacheKey . 'CustomerUserID::' . $Param{CustomerUserID}, ); $Kernel::OM->Get('Kernel::System::Cache')->Delete( Type => $Self->{CacheType}, Key => $CacheKey . 'CustomerID::' . $Param{CustomerID}, ); # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # delete existing relation return if !$DBObject->Do( SQL => 'DELETE FROM customer_user_customer WHERE user_id = ? AND customer_id = ?', Bind => [ \$Param{CustomerUserID}, \$Param{CustomerID} ], ); # return if relation is not active return 1 if !$Param{Active}; # insert new relation return if !$DBObject->Do( SQL => ' INSERT INTO customer_user_customer (user_id, customer_id, create_time, create_by, change_time, change_by) VALUES (?, ?, current_timestamp, ?, current_timestamp, ?)', Bind => [ \$Param{CustomerUserID}, \$Param{CustomerID}, \$Param{UserID}, \$Param{UserID}, ], ); return 1; } =head2 CustomerUserCustomerMemberList() get related customer IDs of a customer user my @CustomerIDs = $CustomerUserObject->CustomerUserCustomerMemberList( CustomerUserID => 123, ); Returns: @CustomerIDs = ( '123', '456', ); get related customer users of a customer ID my @CustomerUsers = $CustomerUserObject->CustomerUserCustomerMemberList( CustomerID => 123, ); Returns: @CustomerUsers = ( '123', '456', ); =cut sub CustomerUserCustomerMemberList { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{CustomerUserID} && !$Param{CustomerID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Got no CustomerUserID or CustomerID!', ); return; } # get needed objects my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); my $CacheKey = 'Cache::CustomerUserCustomerMemberList::'; if ( $Param{CustomerUserID} ) { # check if this result is present (in cache) $CacheKey .= 'CustomerUserID::' . $Param{CustomerUserID}; my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); return @{$Cache} if $Cache; # get customer ids return if !$DBObject->Prepare( SQL => 'SELECT customer_id FROM customer_user_customer WHERE user_id = ? ORDER BY customer_id', Bind => [ \$Param{CustomerUserID}, ], ); # fetch the result my @CustomerIDs; while ( my @Row = $DBObject->FetchrowArray() ) { push @CustomerIDs, $Row[0]; } # cache the result $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, Value => \@CustomerIDs, ); return @CustomerIDs; } else { # check if this result is present (in cache) $CacheKey .= 'CustomerID::' . $Param{CustomerID}; my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get( Type => $Self->{CacheType}, Key => $CacheKey, ); return @{$Cache} if $Cache; # get customer users return if !$DBObject->Prepare( SQL => 'SELECT user_id FROM customer_user_customer WHERE customer_id = ? ORDER BY user_id', Bind => [ \$Param{CustomerID}, ], ); # fetch the result my @CustomerUserIDs; while ( my @Row = $DBObject->FetchrowArray() ) { push @CustomerUserIDs, $Row[0]; } # cache the result $Kernel::OM->Get('Kernel::System::Cache')->Set( Type => $Self->{CacheType}, TTL => $Self->{CacheTTL}, Key => $CacheKey, Value => \@CustomerUserIDs, ); return @CustomerUserIDs; } } =head2 DeleteOnePreference() get customer user preferences my %Preferences = $CustomerUserObject->DeleteOnePreference( UserID => 'some-login', Key => 'PreferenceKey', ); =cut sub DeleteOnePreference { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need UserID!' ); return; } # check if user exists my %User = $Self->CustomerUserDataGet( User => $Param{UserID} ); if ( !%User ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "No such user '$Param{UserID}'!", ); return; } # call new api (2.4.8 and higher) if ( $Self->{ $User{Source} }->can('DeleteOnePreference') ) { return $Self->{ $User{Source} }->DeleteOnePreference(%Param); } # call old api return $Self->{PreferencesObject}->DeleteOnePreference(%Param); } sub DESTROY { my $Self = shift; # execute all transaction events $Self->EventHandlerTransaction(); return 1; } 1;