Files
2024-10-14 00:08:40 +02:00

1973 lines
60 KiB
Perl

# --
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --
package Kernel::System::CustomerUser::DB;
use strict;
use warnings;
use Kernel::System::VariableCheck qw(:all);
use Crypt::PasswdMD5 qw(unix_md5_crypt apache_md5_crypt);
use Digest::SHA;
use Kernel::System::VariableCheck qw(:all);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::Language',
'Kernel::System::Cache',
'Kernel::System::CheckItem',
'Kernel::System::DateTime',
'Kernel::System::DB',
'Kernel::System::DynamicField',
'Kernel::System::DynamicField::Backend',
'Kernel::System::Encode',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::Valid',
'Kernel::System::DynamicField',
'Kernel::System::DynamicField::Backend',
'Kernel::System::DynamicFieldValueObjectName',
);
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# check needed data
for my $Needed (qw( PreferencesObject CustomerUserMap )) {
$Self->{$Needed} = $Param{$Needed} || die "Got no $Needed!";
}
# get database object
$Self->{DBObject} = $Kernel::OM->Get('Kernel::System::DB');
# max shown user per search list
$Self->{UserSearchListLimit} = $Self->{CustomerUserMap}->{CustomerUserSearchListLimit} || 250;
# config options
$Self->{CustomerTable} = $Self->{CustomerUserMap}->{Params}->{Table}
|| die "Need CustomerUser->Params->Table in Kernel/Config.pm!";
$Self->{CustomerKey} = $Self->{CustomerUserMap}->{CustomerKey}
|| $Self->{CustomerUserMap}->{Key}
|| die "Need CustomerUser->CustomerKey in Kernel/Config.pm!";
$Self->{CustomerID} = $Self->{CustomerUserMap}->{CustomerID}
|| die "Need CustomerUser->CustomerID in Kernel/Config.pm!";
$Self->{ReadOnly} = $Self->{CustomerUserMap}->{ReadOnly};
$Self->{ExcludePrimaryCustomerID} = $Self->{CustomerUserMap}->{CustomerUserExcludePrimaryCustomerID} || 0;
$Self->{SearchPrefix} = $Self->{CustomerUserMap}->{CustomerUserSearchPrefix};
if ( !defined $Self->{SearchPrefix} ) {
$Self->{SearchPrefix} = '';
}
$Self->{SearchSuffix} = $Self->{CustomerUserMap}->{CustomerUserSearchSuffix};
if ( !defined $Self->{SearchSuffix} ) {
$Self->{SearchSuffix} = '*';
}
# check if CustomerKey is var or int
ENTRY:
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
if ( $Entry->[0] eq 'UserLogin' && $Entry->[5] =~ /^int$/i ) {
$Self->{CustomerKeyInteger} = 1;
last ENTRY;
}
}
# set cache type
$Self->{CacheType} = 'CustomerUser' . $Param{Count};
# create cache object, but only if CacheTTL is set in customer config
if ( $Self->{CustomerUserMap}->{CacheTTL} ) {
$Self->{CacheObject} = $Kernel::OM->Get('Kernel::System::Cache');
}
# create new db connect if DSN is given
if ( $Self->{CustomerUserMap}->{Params}->{DSN} ) {
$Self->{DBObject} = Kernel::System::DB->new(
DatabaseDSN => $Self->{CustomerUserMap}->{Params}->{DSN},
DatabaseUser => $Self->{CustomerUserMap}->{Params}->{User},
DatabasePw => $Self->{CustomerUserMap}->{Params}->{Password},
%{ $Self->{CustomerUserMap}->{Params} },
) || die('Can\'t connect to database!');
# remember that we have the DBObject not from parent call
$Self->{NotParentDBObject} = 1;
}
# this setting specifies if the table has the create_time,
# create_by, change_time and change_by fields of OTRS
$Self->{ForeignDB} = $Self->{CustomerUserMap}->{Params}->{ForeignDB} ? 1 : 0;
# defines if the database search will be performend case sensitive (1) or not (0)
$Self->{CaseSensitive} = $Self->{CustomerUserMap}->{Params}->{SearchCaseSensitive}
// $Self->{CustomerUserMap}->{Params}->{CaseSensitive} || 0;
# fetch names of configured dynamic fields
my @DynamicFieldMapEntries = grep { $_->[5] eq 'dynamic_field' } @{ $Self->{CustomerUserMap}->{Map} };
$Self->{ConfiguredDynamicFieldNames} = { map { $_->[2] => 1 } @DynamicFieldMapEntries };
return $Self;
}
sub CustomerName {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{UserLogin} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need UserLogin!',
);
return;
}
# check cache
if ( $Self->{CacheObject} ) {
my $Name = $Self->{CacheObject}->Get(
Type => $Self->{CacheType},
Key => "CustomerName::$Param{UserLogin}",
);
return $Name if defined $Name;
}
my $CustomerUserNameFields = $Self->{CustomerUserMap}->{CustomerUserNameFields};
if ( !IsArrayRefWithData($CustomerUserNameFields) ) {
$CustomerUserNameFields = [ 'first_name', 'last_name', ];
}
# remove dynamic field names that are configured in CustomerUserNameFields
# as they cannot be handled here
my @CustomerUserNameFieldsWithoutDynamicFields
= grep { !exists $Self->{ConfiguredDynamicFieldNames}->{$_} } @{$CustomerUserNameFields};
# build SQL string 1/2
my $SQL = "SELECT ";
$SQL .= join( ", ", @CustomerUserNameFieldsWithoutDynamicFields );
$SQL .= " FROM $Self->{CustomerTable} WHERE ";
# check CustomerKey type
my $UserLogin = $Param{UserLogin};
if ( $Self->{CaseSensitive} ) {
$SQL .= "$Self->{CustomerKey} = ?";
}
else {
$SQL .= "LOWER($Self->{CustomerKey}) = LOWER(?)";
}
my %NameParts;
# get data from customer user table
return if !$Self->{DBObject}->Prepare(
SQL => $SQL,
Bind => [ \$Param{UserLogin} ],
Limit => 1,
);
my $FieldCounter = 0;
while ( my @Row = $Self->{DBObject}->FetchrowArray() ) {
for my $Field (@Row) {
$NameParts{ $CustomerUserNameFieldsWithoutDynamicFields[$FieldCounter] } = $Field;
$FieldCounter++;
}
}
# fetch dynamic field values, if configured
my @DynamicFieldCustomerUserNameFields
= grep { exists $Self->{ConfiguredDynamicFieldNames}->{$_} } @{$CustomerUserNameFields};
if (@DynamicFieldCustomerUserNameFields) {
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
DYNAMICFIELDNAME:
for my $DynamicFieldName (@DynamicFieldCustomerUserNameFields) {
my $DynamicFieldConfig = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldGet(
Name => $DynamicFieldName,
);
next DYNAMICFIELDNAME if !IsHashRefWithData($DynamicFieldConfig);
my $Value = $DynamicFieldBackendObject->ValueGet(
DynamicFieldConfig => $DynamicFieldConfig,
ObjectName => $Param{UserLogin},
);
next DYNAMICFIELDNAME if !defined $Value;
if ( !IsArrayRefWithData($Value) ) {
$Value = [$Value];
}
my @RenderedValues;
VALUE:
for my $CurrentValue ( @{$Value} ) {
next VALUE if !defined $CurrentValue || !length $CurrentValue;
my $RenderedValue = $DynamicFieldBackendObject->ReadableValueRender(
DynamicFieldConfig => $DynamicFieldConfig,
Value => $CurrentValue,
);
next VALUE if !IsHashRefWithData($RenderedValue) || !defined $RenderedValue->{Value};
push @RenderedValues, $RenderedValue->{Value};
}
$NameParts{$DynamicFieldName} = join ' ', @RenderedValues;
}
}
# assemble name
my @NameParts;
CUSTOMERUSERNAMEFIELD:
for my $CustomerUserNameField ( @{$CustomerUserNameFields} ) {
next CUSTOMERUSERNAMEFIELD
if !exists $NameParts{$CustomerUserNameField}
|| !defined $NameParts{$CustomerUserNameField}
|| !length $NameParts{$CustomerUserNameField};
push @NameParts, $NameParts{$CustomerUserNameField};
}
my $JoinCharacter = $Self->{CustomerUserMap}->{CustomerUserNameFieldsJoin} // ' ';
my $Name = join $JoinCharacter, @NameParts;
# cache request
if ( $Self->{CacheObject} ) {
$Self->{CacheObject}->Set(
Type => $Self->{CacheType},
Key => "CustomerName::$Param{UserLogin}",
Value => $Name,
TTL => $Self->{CustomerUserMap}->{CacheTTL},
);
}
return $Name;
}
sub CustomerSearch {
my ( $Self, %Param ) = @_;
my %Users;
my $Valid = defined $Param{Valid} ? $Param{Valid} : 1;
# check needed stuff
if (
!$Param{Search}
&& !$Param{UserLogin}
&& !$Param{PostMasterSearch}
&& !$Param{CustomerID}
&& !$Param{CustomerIDRaw}
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need Search, UserLogin, PostMasterSearch, CustomerIDRaw or CustomerID!',
);
return;
}
# check cache
my $CacheKey = join '::', map { $_ . '=' . $Param{$_} } sort keys %Param;
if ( $Self->{CacheObject} ) {
my $Users = $Self->{CacheObject}->Get(
Type => $Self->{CacheType} . '_CustomerSearch',
Key => $CacheKey,
);
return %{$Users} if ref $Users eq 'HASH';
}
my $CustomerUserListFields = $Self->{CustomerUserMap}->{CustomerUserListFields};
if ( !IsArrayRefWithData($CustomerUserListFields) ) {
$CustomerUserListFields = [ 'first_name', 'last_name', 'email', ];
}
# remove dynamic field names that are configured in CustomerUserListFields
# as they cannot be handled here
my @CustomerUserListFieldsWithoutDynamicFields
= grep { !exists $Self->{ConfiguredDynamicFieldNames}->{$_} } @{$CustomerUserListFields};
# build SQL string 1/2
my $SQL = "SELECT $Self->{CustomerKey} ";
my @Bind;
$SQL .= ', ' . ( join ', ', @CustomerUserListFieldsWithoutDynamicFields );
# get like escape string needed for some databases (e.g. oracle)
my $LikeEscapeString = $Self->{DBObject}->GetDatabaseFunction('LikeEscapeString');
# build SQL string 2/2
$SQL .= " FROM $Self->{CustomerTable} WHERE ";
if ( $Param{Search} ) {
if ( !$Self->{CustomerUserMap}->{CustomerUserSearchFields} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"Need CustomerUserSearchFields in CustomerUser config, unable to search for '$Param{Search}'!",
);
return;
}
my $Search = $Self->{DBObject}->QueryStringEscape( QueryString => $Param{Search} );
# remove dynamic field names that are configured in CustomerUserSearchFields
# as they cannot be retrieved here
my @CustomerUserSearchFields = grep { !exists $Self->{ConfiguredDynamicFieldNames}->{$_} }
@{ $Self->{CustomerUserMap}->{CustomerUserSearchFields} };
if ( $Param{CustomerUserOnly} ) {
@CustomerUserSearchFields = grep { $_ ne 'customer_id' } @CustomerUserSearchFields;
}
my %QueryCondition = $Self->{DBObject}->QueryCondition(
Key => \@CustomerUserSearchFields, #$Self->{CustomerUserMap}->{CustomerUserSearchFields},
Value => $Search,
SearchPrefix => $Self->{SearchPrefix},
SearchSuffix => $Self->{SearchSuffix},
CaseSensitive => $Self->{CaseSensitive},
BindMode => 1,
);
$SQL .= $QueryCondition{SQL};
push @Bind, @{ $QueryCondition{Values} };
$SQL .= ' ';
}
elsif ( $Param{PostMasterSearch} ) {
if ( $Self->{CustomerUserMap}->{CustomerUserPostMasterSearchFields} ) {
# remove dynamic field names that are configured in CustomerUserPostMasterSearchFields
# as they cannot be retrieved here
my @CustomerUserPostMasterSearchFields = grep { !exists $Self->{ConfiguredDynamicFieldNames}->{$_} }
@{ $Self->{CustomerUserMap}->{CustomerUserPostMasterSearchFields} };
my $SQLExt = '';
# for my $Field ( @{ $Self->{CustomerUserMap}->{CustomerUserPostMasterSearchFields} } ) {
for my $Field (@CustomerUserPostMasterSearchFields) {
if ($SQLExt) {
$SQLExt .= ' OR ';
}
my $PostMasterSearch = $Self->{DBObject}->Quote( $Param{PostMasterSearch} );
push @Bind, \$PostMasterSearch;
if ( $Self->{CaseSensitive} ) {
$SQLExt .= " $Field = ? ";
}
else {
$SQLExt .= " LOWER($Field) = LOWER(?) ";
}
}
$SQL .= $SQLExt;
}
}
elsif ( $Param{UserLogin} ) {
my $UserLogin = $Param{UserLogin};
# check CustomerKey type
if ( $Self->{CustomerKeyInteger} ) {
# return if login is no integer
return if $Param{UserLogin} !~ /^(\+|\-|)\d{1,16}$/;
$SQL .= "$Self->{CustomerKey} = ?";
push @Bind, \$UserLogin;
}
else {
$UserLogin = '%' . $Self->{DBObject}->Quote( $UserLogin, 'Like' ) . '%';
$UserLogin =~ s/\*/%/g;
push @Bind, \$UserLogin;
if ( $Self->{CaseSensitive} ) {
$SQL .= "$Self->{CustomerKey} LIKE ? $LikeEscapeString";
}
else {
$SQL .= "LOWER($Self->{CustomerKey}) LIKE LOWER(?) $LikeEscapeString";
}
}
}
elsif ( $Param{CustomerID} ) {
my $CustomerID = $Self->{DBObject}->Quote( $Param{CustomerID}, 'Like' );
$CustomerID =~ s/\*/%/g;
push @Bind, \$CustomerID;
if ( $Self->{CaseSensitive} ) {
$SQL .= "$Self->{CustomerID} LIKE ? $LikeEscapeString";
}
else {
$SQL .= "LOWER($Self->{CustomerID}) LIKE LOWER(?) $LikeEscapeString";
}
}
elsif ( $Param{CustomerIDRaw} ) {
push @Bind, \$Param{CustomerIDRaw};
if ( $Self->{CaseSensitive} ) {
$SQL .= "$Self->{CustomerID} = ? ";
}
else {
$SQL .= "LOWER($Self->{CustomerID}) = LOWER(?) ";
}
}
# add valid option
if ( $Self->{CustomerUserMap}->{CustomerValid} && $Valid ) {
# get valid object
my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid');
$SQL .= ' AND '
. $Self->{CustomerUserMap}->{CustomerValid}
. ' IN (' . join( ', ', $ValidObject->ValidIDsGet() ) . ') ';
}
# dynamic field handling
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
my $DynamicFieldConfigs = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
ObjectType => 'CustomerUser',
Valid => 1,
);
my %DynamicFieldConfigsByName = map { $_->{Name} => $_ } @{$DynamicFieldConfigs};
my @CustomerUserListFieldsDynamicFields
= grep { exists $Self->{ConfiguredDynamicFieldNames}->{$_} } @{$CustomerUserListFields};
# get data from customer user table
return if !$Self->{DBObject}->Prepare(
SQL => $SQL,
Bind => \@Bind,
Limit => $Param{Limit} || $Self->{UserSearchListLimit},
);
my @CustomerUserData;
while ( my @Row = $Self->{DBObject}->FetchrowArray() ) {
push @CustomerUserData, [@Row];
}
CUSTOMERUSERDATA:
for my $CustomerUserData (@CustomerUserData) {
my $CustomerKey = shift @{$CustomerUserData};
next CUSTOMERUSERDATA if $Users{$CustomerKey};
my %UserStringParts;
my $FieldCounter = 0;
for my $Field ( @{$CustomerUserData} ) {
$UserStringParts{ $CustomerUserListFieldsWithoutDynamicFields[$FieldCounter] } = $Field;
$FieldCounter++;
}
# fetch dynamic field values, if configured
if (@CustomerUserListFieldsDynamicFields) {
DYNAMICFIELDNAME:
for my $DynamicFieldName (@CustomerUserListFieldsDynamicFields) {
next DYNAMICFIELDNAME if !exists $DynamicFieldConfigsByName{$DynamicFieldName};
my $Value = $DynamicFieldBackendObject->ValueGet(
DynamicFieldConfig => $DynamicFieldConfigsByName{$DynamicFieldName},
ObjectName => $CustomerKey,
);
next DYNAMICFIELDNAME if !defined $Value;
if ( !IsArrayRefWithData($Value) ) {
$Value = [$Value];
}
my @Values;
VALUE:
for my $CurrentValue ( @{$Value} ) {
next VALUE if !defined $CurrentValue || !length $CurrentValue;
my $ReadableValue = $DynamicFieldBackendObject->ReadableValueRender(
DynamicFieldConfig => $DynamicFieldConfigsByName{$DynamicFieldName},
Value => $CurrentValue,
);
next VALUE if !IsHashRefWithData($ReadableValue) || !defined $ReadableValue->{Value};
my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfigsByName{$DynamicFieldName},
Behavior => 'IsACLReducible',
);
if ($IsACLReducible) {
my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet(
DynamicFieldConfig => $DynamicFieldConfigsByName{$DynamicFieldName},
);
if (
IsHashRefWithData($PossibleValues)
&& defined $PossibleValues->{ $ReadableValue->{Value} }
)
{
$ReadableValue->{Value} = $PossibleValues->{ $ReadableValue->{Value} };
}
}
push @Values, $ReadableValue->{Value};
}
$UserStringParts{$DynamicFieldName} = join ' ', @Values;
}
}
# assemble user string
my @UserStringParts;
CUSTOMERUSERLISTFIELD:
for my $CustomerUserListField ( @{$CustomerUserListFields} ) {
next CUSTOMERUSERLISTFIELD
if !exists $UserStringParts{$CustomerUserListField}
|| !defined $UserStringParts{$CustomerUserListField}
|| !length $UserStringParts{$CustomerUserListField};
push @UserStringParts, $UserStringParts{$CustomerUserListField};
}
$Users{$CustomerKey} = join ' ', @UserStringParts;
$Users{$CustomerKey} =~ s/^(.*)\s(.+?\@.+?\..+?)(\s|)$/"$1" <$2>/;
}
# cache request
if ( $Self->{CacheObject} ) {
$Self->{CacheObject}->Set(
Type => $Self->{CacheType} . '_CustomerSearch',
Key => $CacheKey,
Value => \%Users,
TTL => $Self->{CustomerUserMap}->{CacheTTL},
);
}
return %Users;
}
sub CustomerSearchDetail {
my ( $Self, %Param ) = @_;
if ( ref $Param{SearchFields} ne 'ARRAY' ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "SearchFields must be an array reference!",
);
return;
}
my $Valid = defined $Param{Valid} ? $Param{Valid} : 1;
$Param{Limit} //= '';
# Split the search fields in scalar and array fields, before the diffrent handling.
my @ScalarSearchFields = grep { 'Input' eq $_->{Type} } @{ $Param{SearchFields} };
my @ArraySearchFields = grep { 'Selection' eq $_->{Type} } @{ $Param{SearchFields} };
# Verify that all passed array parameters contain an arrayref.
ARGUMENT:
for my $Argument (@ArraySearchFields) {
if ( !defined $Param{ $Argument->{Name} } ) {
$Param{ $Argument->{Name} } ||= [];
next ARGUMENT;
}
if ( ref $Param{ $Argument->{Name} } ne 'ARRAY' ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "$Argument->{Name} must be an array reference!",
);
return;
}
}
# Set the default behaviour for the return type.
my $Result = $Param{Result} || 'ARRAY';
# Special handling if the result type is 'COUNT'.
if ( $Result eq 'COUNT' ) {
# Ignore the parameter 'Limit' when result type is 'COUNT'.
$Param{Limit} = '';
# Delete the OrderBy parameter when the result type is 'COUNT'.
$Param{OrderBy} = [];
}
# Define order table from the search fields.
my %OrderByTable = map { $_->{Name} => $_->{DatabaseField} } @{ $Param{SearchFields} };
for my $Field (@ArraySearchFields) {
my $SelectionsData = $Field->{SelectionsData};
for my $SelectedValue ( @{ $Param{ $Field->{Name} } } ) {
# Check if the selected value for the current field is valid.
if ( !$SelectionsData->{$SelectedValue} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "The selected value $Field->{Name} is not valid!",
);
return;
}
}
}
my $DBObject = $Self->{DBObject};
# Assemble the conditions used in the WHERE clause.
my @SQLWhere;
for my $Field (@ScalarSearchFields) {
# Search for scalar fields (wildcards are allowed).
if ( $Param{ $Field->{Name} } ) {
# Get like escape string needed for some databases (e.g. oracle).
my $LikeEscapeString = $DBObject->GetDatabaseFunction('LikeEscapeString');
$Param{ $Field->{Name} } = $DBObject->Quote( $Param{ $Field->{Name} }, 'Like' );
$Param{ $Field->{Name} } =~ s{ \*+ }{%}xmsg;
# If the field contains more than only %.
if ( $Param{ $Field->{Name} } !~ m{ \A %* \z }xms ) {
push @SQLWhere,
"LOWER($Field->{DatabaseField}) LIKE LOWER('$Param{ $Field->{Name} }') $LikeEscapeString";
}
}
}
my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
# Check all configured change dynamic fields, build lookup hash by name.
my %CustomerUserDynamicFieldName2Config;
my $CustomerUserDynamicFields = $DynamicFieldObject->DynamicFieldListGet(
ObjectType => 'CustomerUser',
);
for my $DynamicField ( @{$CustomerUserDynamicFields} ) {
$CustomerUserDynamicFieldName2Config{ $DynamicField->{Name} } = $DynamicField;
}
my $SQLDynamicFieldFrom = '';
my $SQLDynamicFieldWhere = '';
my $DynamicFieldJoinCounter = 1;
DYNAMICFIELD:
for my $DynamicField ( @{$CustomerUserDynamicFields} ) {
my $SearchParam = $Param{ "DynamicField_" . $DynamicField->{Name} };
next DYNAMICFIELD if ( !$SearchParam );
next DYNAMICFIELD if ( ref $SearchParam ne 'HASH' );
my $NeedJoin;
for my $Operator ( sort keys %{$SearchParam} ) {
my @SearchParams = ( ref $SearchParam->{$Operator} eq 'ARRAY' )
? @{ $SearchParam->{$Operator} }
: ( $SearchParam->{$Operator} );
my $SQLDynamicFieldWhereSub = '';
if ($SQLDynamicFieldWhere) {
$SQLDynamicFieldWhereSub = ' AND (';
}
else {
$SQLDynamicFieldWhereSub = ' (';
}
my $Counter = 0;
TEXT:
for my $Text (@SearchParams) {
next TEXT if ( !defined $Text || $Text eq '' );
$Text =~ s/\*/%/gi;
# Check search attribute, we do not need to search for '*'.
next TEXT if $Text =~ /^\%{1,3}$/;
my $ValidateSuccess = $DynamicFieldBackendObject->ValueValidate(
DynamicFieldConfig => $DynamicField,
Value => $Text,
UserID => $Param{UserID} || 1,
);
if ( !$ValidateSuccess ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Search not executed due to invalid value '"
. $Text
. "' on field '"
. $DynamicField->{Name} . "'!",
);
return;
}
if ($Counter) {
$SQLDynamicFieldWhereSub .= ' OR ';
}
$SQLDynamicFieldWhereSub .= $DynamicFieldBackendObject->SearchSQLGet(
DynamicFieldConfig => $DynamicField,
TableAlias => "dfv$DynamicFieldJoinCounter",
Operator => $Operator,
SearchTerm => $Text,
);
$Counter++;
}
$SQLDynamicFieldWhereSub .= ') ';
if ($Counter) {
$SQLDynamicFieldWhere .= $SQLDynamicFieldWhereSub;
$NeedJoin = 1;
}
}
if ($NeedJoin) {
$SQLDynamicFieldFrom .= "
INNER JOIN dynamic_field_value dfv$DynamicFieldJoinCounter
ON (df_obj_id_name.object_id = dfv$DynamicFieldJoinCounter.object_id
AND dfv$DynamicFieldJoinCounter.field_id = "
. $DBObject->Quote( $DynamicField->{ID}, 'Integer' ) . ")
";
$DynamicFieldJoinCounter++;
}
}
# Execute a dynamic field search, if a dynamic field where statement exists.
if ($SQLDynamicFieldWhere) {
my @DynamicFieldUserLogins;
my $SQLDynamicField
= "SELECT DISTINCT(df_obj_id_name.object_name) FROM dynamic_field_obj_id_name df_obj_id_name "
. $SQLDynamicFieldFrom
. " WHERE "
. $SQLDynamicFieldWhere;
my $UsedCache;
if ( $Self->{CacheObject} ) {
my $DynamicFieldSearchCacheData = $Self->{CacheObject}->Get(
Type => $Self->{CacheType} . '_CustomerSearchDetailDynamicFields',
Key => $SQLDynamicField,
);
if ( defined $DynamicFieldSearchCacheData ) {
if ( ref $DynamicFieldSearchCacheData eq 'ARRAY' ) {
@DynamicFieldUserLogins = @{$DynamicFieldSearchCacheData};
$UsedCache = 1;
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Invalid ref ' . ref($DynamicFieldSearchCacheData) . '!'
);
return;
}
}
}
# Get the data only from database, if no cache entry exists.
if ( !$UsedCache ) {
return if !$DBObject->Prepare(
SQL => $SQLDynamicField,
);
while ( my @Row = $DBObject->FetchrowArray() ) {
push @DynamicFieldUserLogins, $Row[0];
}
if ( $Self->{CacheObject} ) {
$Self->{CacheObject}->Set(
Type => $Self->{CacheType} . '_CustomerSearchDetailDynamicFields',
Key => $SQLDynamicField,
Value => \@DynamicFieldUserLogins,
TTL => $Self->{CustomerUserMap}->{CacheTTL},
);
}
}
# Add the user logins from the dynamic fields, if a search result exists from the dynamic field search
# or skip the search and return a emptry array ref, if no user logins exists from the dynamic field search.
if (@DynamicFieldUserLogins) {
my $SQLQueryInCondition = $DBObject->QueryInCondition(
Key => $Self->{CustomerKey},
Values => \@DynamicFieldUserLogins,
BindMode => 0,
);
push @SQLWhere, $SQLQueryInCondition;
}
else {
return $Result eq 'COUNT' ? 0 : [];
}
}
FIELD:
for my $Field (@ArraySearchFields) {
next FIELD if !@{ $Param{ $Field->{Name} } };
my $SQLQueryInCondition = $DBObject->QueryInCondition(
Key => $Field->{DatabaseField},
Values => $Param{ $Field->{Name} },
BindMode => 0,
);
push @SQLWhere, $SQLQueryInCondition;
}
# Special parameter for CustomerIDs from a customer company search result.
if ( IsArrayRefWithData( $Param{CustomerCompanySearchCustomerIDs} ) ) {
my $SQLQueryInCondition = $DBObject->QueryInCondition(
Key => $Self->{CustomerID},
Values => $Param{CustomerCompanySearchCustomerIDs},
BindMode => 0,
);
push @SQLWhere, $SQLQueryInCondition;
}
# Special parameter to exclude some user logins from the search result.
if ( IsArrayRefWithData( $Param{ExcludeUserLogins} ) ) {
my $SQLQueryInCondition = $DBObject->QueryInCondition(
Key => $Self->{CustomerKey},
Values => $Param{ExcludeUserLogins},
BindMode => 0,
Negate => 1,
);
push @SQLWhere, $SQLQueryInCondition;
}
# Add the valid option if needed.
if ( $Self->{CustomerUserMap}->{CustomerValid} && $Valid ) {
my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid');
push @SQLWhere,
"$Self->{CustomerUserMap}->{CustomerValid} IN (" . join( ', ', $ValidObject->ValidIDsGet() ) . ") ";
}
# Check if OrderBy contains only unique valid values.
my %OrderBySeen;
for my $OrderBy ( @{ $Param{OrderBy} } ) {
if ( !$OrderBy || $OrderBySeen{$OrderBy} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "OrderBy contains invalid value '$OrderBy' or the value is used more than once!",
);
return;
}
# Remember the value to check if it appears more than once.
$OrderBySeen{$OrderBy} = 1;
}
# Check if OrderByDirection array contains only 'Up' or 'Down'.
DIRECTION:
for my $Direction ( @{ $Param{OrderByDirection} } ) {
# Only 'Up' or 'Down' allowed.
next DIRECTION if $Direction eq 'Up';
next DIRECTION if $Direction eq 'Down';
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "OrderByDirection can only contain 'Up' or 'Down'!",
);
return;
}
# Build the sql statement for the search.
my $SQL = "SELECT DISTINCT($Self->{CustomerKey})";
# Modify SQL when the result type is 'COUNT'.
if ( $Result eq 'COUNT' ) {
$SQL = "SELECT COUNT(DISTINCT($Self->{CustomerKey}))";
}
# Assemble the ORDER BY clause.
my @SQLOrderBy;
# The Order by clause is not needed for the result type 'COUNT'.
if ( $Result ne 'COUNT' ) {
my $Count = 0;
ORDERBY:
for my $OrderBy ( @{ $Param{OrderBy} } ) {
my $Direction = 'DESC';
if ( $Param{OrderByDirection}->[$Count] ) {
if ( $Param{OrderByDirection}->[$Count] eq 'Up' ) {
$Direction = 'ASC';
}
elsif ( $Param{OrderByDirection}->[$Count] eq 'Down' ) {
$Direction = 'DESC';
}
}
$Count++;
next ORDERBY if !$OrderByTable{$OrderBy};
push @SQLOrderBy, "$OrderByTable{$OrderBy} $Direction";
next ORDERBY if $OrderBy eq 'UserLogin';
$SQL .= ", $OrderByTable{$OrderBy}";
}
# If there is a possibility that the ordering is not determined
# we add an descending ordering by id.
if ( !grep { $_ eq 'UserLogin' } ( @{ $Param{OrderBy} } ) ) {
push @SQLOrderBy, "$Self->{CustomerKey} DESC";
}
}
$SQL .= " FROM $Self->{CustomerTable} ";
if (@SQLWhere) {
my $SQLWhereString = join ' AND ', map {"( $_ )"} @SQLWhere;
$SQL .= "WHERE $SQLWhereString ";
}
if (@SQLOrderBy) {
my $OrderByString = join ', ', @SQLOrderBy;
$SQL .= "ORDER BY $OrderByString";
}
# Check if a cache exists before we ask the database.
if ( $Self->{CacheObject} ) {
my $CacheData = $Self->{CacheObject}->Get(
Type => $Self->{CacheType} . '_CustomerSearchDetail',
Key => $SQL . $Param{Limit},
);
if ( defined $CacheData ) {
if ( ref $CacheData eq 'ARRAY' ) {
return $CacheData;
}
elsif ( ref $CacheData eq '' ) {
return $CacheData;
}
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Invalid ref ' . ref($CacheData) . '!'
);
return;
}
}
return if !$DBObject->Prepare(
SQL => $SQL,
Limit => $Param{Limit},
);
my @IDs;
while ( my @Row = $DBObject->FetchrowArray() ) {
push @IDs, $Row[0];
}
# Handle the diffrent result types.
if ( $Result eq 'COUNT' ) {
if ( $Self->{CacheObject} ) {
$Self->{CacheObject}->Set(
Type => $Self->{CacheType} . '_CustomerSearchDetail',
Key => $SQL . $Param{Limit},
Value => $IDs[0],
TTL => $Self->{CustomerUserMap}->{CacheTTL},
);
}
return $IDs[0];
}
else {
if ( $Self->{CacheObject} ) {
$Self->{CacheObject}->Set(
Type => $Self->{CacheType} . '_CustomerSearchDetail',
Key => $SQL . $Param{Limit},
Value => \@IDs,
TTL => $Self->{CustomerUserMap}->{CacheTTL},
);
}
return \@IDs;
}
}
sub CustomerIDList {
my ( $Self, %Param ) = @_;
my $Valid = defined $Param{Valid} ? $Param{Valid} : 1;
my $SearchTerm = $Param{SearchTerm} || '';
my $CacheType = $Self->{CacheType} . '_CustomerIDList';
my $CacheKey = "CustomerIDList::${Valid}::$SearchTerm";
# check cache
if ( $Self->{CacheObject} ) {
my $Result = $Self->{CacheObject}->Get(
Type => $CacheType,
Key => $CacheKey,
);
return @{$Result} if ref $Result eq 'ARRAY';
}
my $SQL = "
SELECT DISTINCT($Self->{CustomerID})
FROM $Self->{CustomerTable}
WHERE 1 = 1 ";
my @Bind;
# add valid option
if ( $Self->{CustomerUserMap}->{CustomerValid} && $Valid ) {
# get valid object
my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid');
my $ValidIDs = join( ', ', $ValidObject->ValidIDsGet() );
$SQL .= "
AND $Self->{CustomerUserMap}->{CustomerValid} IN ($ValidIDs) ";
}
# add search term
if ($SearchTerm) {
my $SearchTermEscaped = $Self->{DBObject}->QueryStringEscape( QueryString => $SearchTerm );
$SQL .= ' AND ';
my %QueryCondition = $Self->{DBObject}->QueryCondition(
Key => $Self->{CustomerID},
Value => $SearchTermEscaped,
SearchPrefix => $Self->{SearchPrefix},
SearchSuffix => $Self->{SearchSuffix},
CaseSensitive => $Self->{CaseSensitive},
BindMode => 1,
);
$SQL .= $QueryCondition{SQL};
push @Bind, @{ $QueryCondition{Values} };
$SQL .= ' ';
}
return if !$Self->{DBObject}->Prepare(
SQL => $SQL,
Bind => \@Bind,
);
my @Result;
while ( my @Row = $Self->{DBObject}->FetchrowArray() ) {
push @Result, $Row[0];
}
# cache request
if ( $Self->{CacheObject} ) {
$Self->{CacheObject}->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \@Result,
TTL => $Self->{CustomerUserMap}->{CacheTTL},
);
}
return @Result;
}
sub CustomerIDs {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{User} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need User!'
);
return;
}
# check cache
if ( $Self->{CacheObject} ) {
my $CustomerIDs = $Self->{CacheObject}->Get(
Type => $Self->{CacheType},
Key => "CustomerIDs::$Param{User}",
);
return @{$CustomerIDs} if ref $CustomerIDs eq 'ARRAY';
}
# get customer data
my %Data = $Self->CustomerUserDataGet(
User => $Param{User},
);
# there are multi customer ids
my @CustomerIDs;
if ( $Data{UserCustomerIDs} ) {
# used separators
SEPARATOR:
for my $Separator ( ';', ',', '|' ) {
next SEPARATOR if $Data{UserCustomerIDs} !~ /\Q$Separator\E/;
# split it
my @IDs = split /\Q$Separator\E/, $Data{UserCustomerIDs};
for my $ID (@IDs) {
$ID =~ s/^\s+//g;
$ID =~ s/\s+$//g;
push @CustomerIDs, $ID;
}
last SEPARATOR;
}
# fallback if no separator got found
if ( !@CustomerIDs ) {
$Data{UserCustomerIDs} =~ s/^\s+//g;
$Data{UserCustomerIDs} =~ s/\s+$//g;
push @CustomerIDs, $Data{UserCustomerIDs};
}
}
# use also the primary customer id
if ( $Data{UserCustomerID} && !$Self->{ExcludePrimaryCustomerID} ) {
push @CustomerIDs, $Data{UserCustomerID};
}
# cache request
if ( $Self->{CacheObject} ) {
$Self->{CacheObject}->Set(
Type => $Self->{CacheType},
Key => 'CustomerIDs::' . $Param{User},
Value => \@CustomerIDs,
TTL => $Self->{CustomerUserMap}->{CacheTTL},
);
}
return @CustomerIDs;
}
sub CustomerUserDataGet {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{User} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need User!',
);
return;
}
# build select
my $SQL = 'SELECT ';
ENTRY:
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
next ENTRY if $Entry->[5] eq 'dynamic_field';
$SQL .= " $Entry->[2], ";
}
if ( !$Self->{ForeignDB} ) {
$SQL .= "create_time, create_by, change_time, change_by, ";
}
$SQL .= $Self->{CustomerKey} . " FROM $Self->{CustomerTable} WHERE ";
# check cache
if ( $Self->{CacheObject} ) {
my $Data = $Self->{CacheObject}->Get(
Type => $Self->{CacheType},
Key => "CustomerUserDataGet::$Param{User}",
);
return %{$Data} if ref $Data eq 'HASH';
}
# check customer key type
my $User = $Param{User};
if ( $Self->{CaseSensitive} ) {
$SQL .= "$Self->{CustomerKey} = ?";
}
else {
$SQL .= "LOWER($Self->{CustomerKey}) = LOWER(?)";
}
# ask the database
return if !$Self->{DBObject}->Prepare(
SQL => $SQL,
Bind => [ \$User ],
Limit => 1,
);
# fetch the result
my %Data;
ROW:
while ( my @Row = $Self->{DBObject}->FetchrowArray() ) {
my $MapCounter = 0;
ENTRY:
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
next ENTRY if $Entry->[5] eq 'dynamic_field';
$Data{ $Entry->[0] } = $Row[$MapCounter];
$MapCounter++;
}
next ROW if $Self->{ForeignDB};
for my $Key (qw(CreateTime CreateBy ChangeTime ChangeBy)) {
$Data{$Key} = $Row[$MapCounter];
$MapCounter++;
}
}
# check data
if ( !$Data{UserLogin} ) {
# cache request
if ( $Self->{CacheObject} ) {
$Self->{CacheObject}->Set(
Type => $Self->{CacheType},
Key => "CustomerUserDataGet::$Param{User}",
Value => {},
TTL => $Self->{CustomerUserMap}->{CacheTTL},
);
}
return;
}
my $CustomerUserListFieldsMap = $Self->{CustomerUserMap}->{CustomerUserListFields};
if ( !IsArrayRefWithData($CustomerUserListFieldsMap) ) {
$CustomerUserListFieldsMap = [ 'first_name', 'last_name', 'email', ];
}
# Order fields by CustomerUserListFields (see bug#13821).
my @CustomerUserListFields;
for my $Field ( @{$CustomerUserListFieldsMap} ) {
my @FieldNames = map { $_->[0] } grep { $_->[2] eq $Field } @{ $Self->{CustomerUserMap}->{Map} };
push @CustomerUserListFields, $FieldNames[0];
}
my $UserMailString = '';
my @UserMailStringParts;
FIELD:
for my $Field (@CustomerUserListFields) {
next FIELD if !$Data{$Field};
push @UserMailStringParts, $Data{$Field};
}
$UserMailString = join ' ', @UserMailStringParts;
$UserMailString =~ s/^(.*)\s(.+?\@.+?\..+?)(\s|)$/"$1" <$2>/;
# add the UserMailString to the data hash
$Data{UserMailString} = $UserMailString;
# compat!
$Data{UserID} = $Data{UserLogin};
# get preferences
my %Preferences = $Self->GetPreferences( UserID => $Data{UserID} );
# add last login timestamp
if ( $Preferences{UserLastLogin} ) {
my $DateTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
Epoch => $Preferences{UserLastLogin},
},
);
$Preferences{UserLastLoginTimestamp} = $DateTimeObject->ToString();
}
# cache request
if ( $Self->{CacheObject} ) {
$Self->{CacheObject}->Set(
Type => $Self->{CacheType},
Key => "CustomerUserDataGet::$Param{User}",
Value => { %Data, %Preferences },
TTL => $Self->{CustomerUserMap}->{CacheTTL},
);
}
return ( %Data, %Preferences );
}
sub CustomerUserAdd {
my ( $Self, %Param ) = @_;
# check ro/rw
if ( $Self->{ReadOnly} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Customer backend is read only!',
);
return;
}
# check needed stuff
ENTRY:
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
if ( !$Param{ $Entry->[0] } && $Entry->[4] ) {
# skip UserLogin, will be checked later
next ENTRY if ( $Entry->[0] eq 'UserLogin' );
# ignore dynamic fields here
next ENTRY if $Entry->[5] eq 'dynamic_field';
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Entry->[0]!",
);
return;
}
}
if ( !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need UserID!',
);
return;
}
# if no UserLogin is given
if ( !$Param{UserLogin} && $Self->{CustomerUserMap}->{AutoLoginCreation} ) {
# get time object
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $DateTimeString = $DateTimeObject->Format( Format => '%Y%m%d%H%M' );
my $Prefix = $Self->{CustomerUserMap}->{AutoLoginCreationPrefix} || 'auto';
$Param{UserLogin} = "$Prefix-$DateTimeString" . int( rand(99) );
}
# check if user login exists
if ( !$Param{UserLogin} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need UserLogin!',
);
return;
}
# check email address if already exists
if ( $Param{UserEmail} && $Self->{CustomerUserMap}->{CustomerUserEmailUniqCheck} ) {
my %Result = $Self->CustomerSearch(
Valid => 0,
PostMasterSearch => $Param{UserEmail},
);
if (%Result) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => $Kernel::OM->Get('Kernel::Language')
->Translate('This email address is already in use for another customer user.'),
);
return;
}
}
# get check item object
my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem');
# check email address mx
if (
$Param{UserEmail}
&& !$CheckItemObject->CheckEmail( Address => $Param{UserEmail} )
&& grep { $_ eq $Param{ValidID} } $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet()
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Email address ($Param{UserEmail}) not valid ("
. $CheckItemObject->CheckError() . ")!",
);
return;
}
# quote values
my %Value;
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
if ( $Entry->[5] =~ /^int$/i ) {
if ( $Param{ $Entry->[0] } ) {
$Value{ $Entry->[0] } = $Param{ $Entry->[0] };
}
else {
$Value{ $Entry->[0] } = 0;
}
}
else {
if ( $Param{ $Entry->[0] } ) {
$Value{ $Entry->[0] } = $Param{ $Entry->[0] };
}
else {
$Value{ $Entry->[0] } = '';
}
}
}
# build insert
my $SQL = "INSERT INTO $Self->{CustomerTable} (";
my @Bind;
my %SeenKey; # If the map contains duplicated field names, insert only once.
my @ColumnNames;
MAPENTRY:
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
next MAPENTRY if $Entry->[5] eq 'dynamic_field'; # skip dynamic fields
next MAPENTRY if ( lc( $Entry->[0] ) eq "userpassword" );
next MAPENTRY if $SeenKey{ $Entry->[2] }++;
push @ColumnNames, $Entry->[2];
}
$SQL .= join ', ', @ColumnNames;
if ( !$Self->{ForeignDB} ) {
$SQL .= ', create_time, create_by, change_time, change_by';
}
$SQL .= ') VALUES (';
my %SeenValue;
my $BindColumns = 0;
ENTRY:
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
next ENTRY if $Entry->[5] eq 'dynamic_field'; # skip dynamic fields
next ENTRY if ( lc( $Entry->[0] ) eq "userpassword" );
next ENTRY if $SeenValue{ $Entry->[2] }++;
$BindColumns++;
push @Bind, \$Value{ $Entry->[0] };
}
$SQL .= join ', ', ('?') x $BindColumns;
if ( !$Self->{ForeignDB} ) {
$SQL .= ', current_timestamp, ?, current_timestamp, ?';
push @Bind, \$Param{UserID};
push @Bind, \$Param{UserID};
}
$SQL .= ')';
return if !$Self->{DBObject}->Do(
SQL => $SQL,
Bind => \@Bind
);
# log notice
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'info',
Message => "CustomerUser: '$Param{UserLogin}' created successfully ($Param{UserID})!",
);
# set password
if ( $Param{UserPassword} ) {
$Self->SetPassword(
UserLogin => $Param{UserLogin},
PW => $Param{UserPassword}
);
}
$Self->_CustomerUserCacheClear( UserLogin => $Param{UserLogin} );
return $Param{UserLogin};
}
sub CustomerUserUpdate {
my ( $Self, %Param ) = @_;
# check ro/rw
if ( $Self->{ReadOnly} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Customer backend is read only!',
);
return;
}
# check needed stuff
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
if (
!$Param{ $Entry->[0] }
&& $Entry->[5] ne 'dynamic_field' # ignore dynamic fields here
&& $Entry->[4] # only check required fields
&& $Entry->[0] ne 'UserPassword' # ignore UserPassword field
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Entry->[0]!",
);
return;
}
}
# get check item object
my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem');
# check email address
if (
$Param{UserEmail}
&& !$CheckItemObject->CheckEmail( Address => $Param{UserEmail} )
&& grep { $_ eq $Param{ValidID} } $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet()
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Email address ($Param{UserEmail}) not valid ("
. $CheckItemObject->CheckError() . ")!",
);
return;
}
# get old user data (pw)
my %UserData = $Self->CustomerUserDataGet( User => $Param{ID} );
# if we update the email address, check if it already exists
if (
$Param{UserEmail}
&& $Self->{CustomerUserMap}->{CustomerUserEmailUniqCheck}
&& lc $Param{UserEmail} ne lc $UserData{UserEmail}
)
{
my %Result = $Self->CustomerSearch(
Valid => 0,
PostMasterSearch => $Param{UserEmail},
);
if (%Result) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => $Kernel::OM->Get('Kernel::Language')
->Translate('This email address is already in use for another customer user.'),
);
return;
}
}
# quote values
my %Value;
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
if ( $Entry->[5] =~ /^int$/i ) {
if ( $Param{ $Entry->[0] } ) {
$Value{ $Entry->[0] } = $Param{ $Entry->[0] };
}
else {
$Value{ $Entry->[0] } = 0;
}
}
else {
if ( $Param{ $Entry->[0] } ) {
$Value{ $Entry->[0] } = $Param{ $Entry->[0] };
}
else {
$Value{ $Entry->[0] } = "";
}
}
}
# update db
my $SQL = "UPDATE $Self->{CustomerTable} SET ";
my @Bind;
my %SeenKey; # If the map contains duplicated field names, insert only once.
ENTRY:
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
next ENTRY if $Entry->[5] eq 'dynamic_field'; # skip dynamic fields
next ENTRY if $Entry->[7]; # skip readonly fields
next ENTRY if ( lc( $Entry->[0] ) eq "userpassword" );
next ENTRY if $SeenKey{ $Entry->[2] }++;
$SQL .= " $Entry->[2] = ?, ";
push @Bind, \$Value{ $Entry->[0] };
}
if ( !$Self->{ForeignDB} ) {
$SQL .= 'change_time = current_timestamp, change_by = ?';
push @Bind, \$Param{UserID};
}
else {
chop $SQL;
chop $SQL;
}
$SQL .= ' WHERE ';
if ( $Self->{CaseSensitive} ) {
$SQL .= "$Self->{CustomerKey} = ?";
}
else {
$SQL .= "LOWER($Self->{CustomerKey}) = LOWER(?)";
}
push @Bind, \$Param{ID};
return if !$Self->{DBObject}->Do(
SQL => $SQL,
Bind => \@Bind
);
# check if we need to update Customer Preferences
if ( $Param{UserLogin} ne $UserData{UserLogin} ) {
# update the preferences
$Self->{PreferencesObject}->RenamePreferences(
NewUserID => $Param{UserLogin},
OldUserID => $UserData{UserLogin},
);
}
# log notice
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'info',
Message => "CustomerUser: '$Param{UserLogin}' updated successfully ($Param{UserID})!",
);
# check pw
if ( $Param{UserPassword} ) {
$Self->SetPassword(
UserLogin => $Param{UserLogin},
PW => $Param{UserPassword}
);
}
$Self->_CustomerUserCacheClear( UserLogin => $Param{UserLogin} );
if ( $Param{UserLogin} ne $UserData{UserLogin} ) {
$Self->_CustomerUserCacheClear( UserLogin => $UserData{UserLogin} );
}
return 1;
}
sub SetPassword {
my ( $Self, %Param ) = @_;
my $Login = $Param{UserLogin};
my $Pw = $Param{PW} || '';
# check ro/rw
if ( $Self->{ReadOnly} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Customer backend is read only!',
);
return;
}
# check needed stuff
if ( !$Param{UserLogin} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need UserLogin!',
);
return;
}
my $CryptedPw = '';
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $CryptType = $ConfigObject->Get('Customer::AuthModule::DB::CryptType') || 'sha2';
# get encode object
my $EncodeObject = $Kernel::OM->Get('Kernel::System::Encode');
# crypt plain (no crypt at all)
if ( $CryptType eq 'plain' ) {
$CryptedPw = $Pw;
}
# crypt with unix crypt
elsif ( $CryptType eq 'crypt' ) {
# encode output, needed by crypt() only non utf8 signs
$EncodeObject->EncodeOutput( \$Pw );
$EncodeObject->EncodeOutput( \$Login );
$CryptedPw = crypt( $Pw, $Login );
$EncodeObject->EncodeInput( \$CryptedPw );
}
# crypt with md5 crypt
elsif ( $CryptType eq 'md5' || !$CryptType ) {
# encode output, needed by unix_md5_crypt() only non utf8 signs
$EncodeObject->EncodeOutput( \$Pw );
$EncodeObject->EncodeOutput( \$Login );
$CryptedPw = unix_md5_crypt( $Pw, $Login );
$EncodeObject->EncodeInput( \$CryptedPw );
}
# crypt with md5 crypt (compatible with Apache's .htpasswd files)
elsif ( $CryptType eq 'apr1' ) {
# encode output, needed by apache_md5_crypt() only non utf8 signs
$EncodeObject->EncodeOutput( \$Pw );
$EncodeObject->EncodeOutput( \$Login );
$CryptedPw = apache_md5_crypt( $Pw, $Login );
$EncodeObject->EncodeInput( \$CryptedPw );
}
# crypt with sha1
elsif ( $CryptType eq 'sha1' ) {
my $SHAObject = Digest::SHA->new('sha1');
$EncodeObject->EncodeOutput( \$Pw );
$SHAObject->add($Pw);
$CryptedPw = $SHAObject->hexdigest();
}
elsif ( $CryptType eq 'sha512' ) {
my $SHAObject = Digest::SHA->new('sha512');
$EncodeObject->EncodeOutput( \$Pw );
$SHAObject->add($Pw);
$CryptedPw = $SHAObject->hexdigest();
}
# bcrypt
elsif ( $CryptType eq 'bcrypt' ) {
# get main object
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
if ( !$MainObject->Require('Crypt::Eksblowfish::Bcrypt') ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"CustomerUser: '$Login' tried to store password with bcrypt but 'Crypt::Eksblowfish::Bcrypt' is not installed!",
);
return;
}
my $Cost = $ConfigObject->Get('Customer::AuthModule::DB::bcryptCost') // 12;
# Don't allow values smaller than 9 for security.
$Cost = 9 if $Cost < 9;
# Current Crypt::Eksblowfish::Bcrypt limit is 31.
$Cost = 31 if $Cost > 31;
my $Salt = $MainObject->GenerateRandomString( Length => 16 );
# remove UTF8 flag, required by Crypt::Eksblowfish::Bcrypt
$EncodeObject->EncodeOutput( \$Pw );
# calculate password hash
my $Octets = Crypt::Eksblowfish::Bcrypt::bcrypt_hash(
{
key_nul => 1,
cost => $Cost,
salt => $Salt,
},
$Pw
);
# We will store cost and salt in the password string so that it can be decoded
# in future even if we use a higher cost by default.
$CryptedPw = "BCRYPT:$Cost:$Salt:" . Crypt::Eksblowfish::Bcrypt::en_base64($Octets);
}
# crypt with sha2 as fallback
else {
my $SHAObject = Digest::SHA->new('sha256');
# encode output, needed by sha256_hex() only non utf8 signs
$EncodeObject->EncodeOutput( \$Pw );
$SHAObject->add($Pw);
$CryptedPw = $SHAObject->hexdigest();
}
# update db
for my $Entry ( @{ $Self->{CustomerUserMap}->{Map} } ) {
if ( $Entry->[0] =~ /^UserPassword$/i ) {
$Param{PasswordCol} = $Entry->[2];
}
if ( $Entry->[0] =~ /^UserLogin$/i ) {
$Param{LoginCol} = $Entry->[2];
}
}
# check if needed pw col. exists (else there is no pw col.)
if ( $Param{PasswordCol} && $Param{LoginCol} ) {
my $SQL = "UPDATE $Self->{CustomerTable} SET "
. " $Param{PasswordCol} = ? "
. " WHERE ";
if ( $Self->{CaseSensitive} ) {
$SQL .= "$Param{LoginCol} = ?";
}
else {
$SQL .= "LOWER($Param{LoginCol}) = LOWER(?)";
}
return if !$Self->{DBObject}->Do(
SQL => $SQL,
Bind => [ \$CryptedPw, \$Param{UserLogin} ],
);
# log notice
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "CustomerUser: '$Param{UserLogin}' changed password successfully!",
);
$Self->_CustomerUserCacheClear( UserLogin => $Param{UserLogin} );
return 1;
}
# need no pw to set
return 1;
}
sub GenerateRandomPassword {
my ( $Self, %Param ) = @_;
# generated passwords are eight characters long by default
my $Size = $Param{Size} || 8;
my $Password = $Kernel::OM->Get('Kernel::System::Main')->GenerateRandomString(
Length => $Size,
);
return $Password;
}
sub SetPreferences {
my ( $Self, %Param ) = @_;
# check needed params
if ( !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need UserID!',
);
return;
}
$Self->_CustomerUserCacheClear( UserLogin => $Param{UserID} );
return $Self->{PreferencesObject}->SetPreferences(%Param);
}
sub GetPreferences {
my ( $Self, %Param ) = @_;
# check needed params
if ( !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need UserID!',
);
return;
}
return $Self->{PreferencesObject}->GetPreferences(%Param);
}
sub SearchPreferences {
my ( $Self, %Param ) = @_;
return $Self->{PreferencesObject}->SearchPreferences(%Param);
}
sub _CustomerUserCacheClear {
my ( $Self, %Param ) = @_;
return if !$Self->{CacheObject};
if ( !$Param{UserLogin} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need UserLogin!',
);
return;
}
$Self->{CacheObject}->Delete(
Type => $Self->{CacheType},
Key => "CustomerUserDataGet::$Param{UserLogin}",
);
$Self->{CacheObject}->Delete(
Type => $Self->{CacheType},
Key => "CustomerName::$Param{UserLogin}",
);
$Self->{CacheObject}->Delete(
Type => $Self->{CacheType},
Key => "CustomerIDs::$Param{UserLogin}",
);
# delete all search cache entries
$Self->{CacheObject}->CleanUp(
Type => $Self->{CacheType} . '_CustomerIDList',
);
$Self->{CacheObject}->CleanUp(
Type => $Self->{CacheType} . '_CustomerSearch',
);
$Self->{CacheObject}->CleanUp(
Type => $Self->{CacheType} . '_CustomerSearchDetail',
);
$Self->{CacheObject}->CleanUp(
Type => $Self->{CacheType} . '_CustomerSearchDetailDynamicFields',
);
$Self->{CacheObject}->CleanUp(
Type => 'CustomerGroup',
);
for my $Function (qw(CustomerUserList)) {
for my $Valid ( 0 .. 1 ) {
$Self->{CacheObject}->Delete(
Type => $Self->{CacheType},
Key => "${Function}::${Valid}",
);
}
}
return 1;
}
sub DESTROY {
my $Self = shift;
# disconnect if it's not a parent DBObject
if ( $Self->{NotParentDBObject} ) {
if ( $Self->{DBObject} ) {
$Self->{DBObject}->Disconnect();
}
}
return 1;
}
1;