diff --git a/Custom/Kernel/System/CustomerUser.pm b/Custom/Kernel/System/CustomerUser.pm
new file mode 100644
index 0000000..74323b2
--- /dev/null
+++ b/Custom/Kernel/System/CustomerUser.pm
@@ -0,0 +1,1588 @@
+# --
+# 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;
diff --git a/Kernel/System/Console/Command/Admin/CustomerUser/List.pm b/Kernel/System/Console/Command/Admin/CustomerUser/List.pm
new file mode 100644
index 0000000..82b08a8
--- /dev/null
+++ b/Kernel/System/Console/Command/Admin/CustomerUser/List.pm
@@ -0,0 +1,75 @@
+# --
+# 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::Console::Command::Admin::CustomerUser::Add;
+
+use strict;
+use warnings;
+
+use parent qw(Kernel::System::Console::BaseCommand);
+
+our @ObjectDependencies = (
+ 'Kernel::System::CustomerUser',
+);
+
+sub Configure {
+ my ( $Self, %Param ) = @_;
+
+ $Self->Description('List or search for customer users.');
+ $Self->AddOption(
+ Name => 'search-term',
+ Description => "Term to search by for customer users.",
+ Required => 0,
+ HasValue => 1,
+ ValueRegex => qr/.*/smx,
+ );
+ $Self->AddOption(
+ Name => 'source',
+ Description => "Filter by one or more comma-separated sources.",
+ Required => 0,
+ HasValue => 1,
+ ValueRegex => qr/.*/smx,
+ );
+ $Self->AddOption(
+ Name => 'output-format',
+ Description => "Define output format - default is 'plain'.",
+ Required => 0,
+ HasValue => 1,
+ ValueRegex => qr/(json|plain|markdown|xml)/smx,
+ );
+ $Self->AddOption(
+ Name => 'output-columns',
+ Description => "Comma-separated output columns. Possible are login, email, customercompany, title, firstname, lastname, phone, fax, mobile, street, zip, city, country, comments, valid. Default are login and email.",
+ Required => 0,
+ HasValue => 1,
+ ValueRegex => qr/((login|email|customercompany|title|firstname|lastname|phone|fax|mobile|street|zip|city|country|comments|valid),?)*/smx,
+ );
+
+ return;
+}
+
+sub Run {
+ my ( $Self, %Param ) = @_;
+
+ $Self->Print("Fetching customer users...\n");
+
+ my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser');
+
+ $Self->Print("Done.\n");
+ return $Self->ExitCodeOk();
+}
+
+1;