Files
scripts/Perl OTRS/Kernel/System/SysConfig.pm
2024-10-14 00:08:40 +02:00

6333 lines
197 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.
# --
## nofilter(TidyAll::Plugin::OTRS::Perl::LayoutObject)
package Kernel::System::SysConfig;
use strict;
use warnings;
use Time::HiRes();
use utf8;
use Kernel::System::VariableCheck qw(:all);
use Kernel::Language qw(Translatable);
use Kernel::Config;
use parent qw(Kernel::System::AsynchronousExecutor);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::Language',
'Kernel::Output::HTML::SysConfig',
'Kernel::System::Cache',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::Package',
'Kernel::System::Storable',
'Kernel::System::SysConfig::DB',
'Kernel::System::SysConfig::Migration',
'Kernel::System::SysConfig::XML',
'Kernel::System::User',
'Kernel::System::YAML',
);
=head1 NAME
Kernel::System::SysConfig - Functions to manage system configuration settings.
=head1 DESCRIPTION
All functions to manage system configuration settings.
=head1 PUBLIC INTERFACE
=head2 new()
Don't use the constructor directly, use the ObjectManager instead:
my $SysConfigObject = $Kernel::OM->Get('Kernel::System::SysConfig');
=cut
## no critic (StringyEval)
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
$Self->{ConfigObject} = $Kernel::OM->Get('Kernel::Config');
# get home directory
$Self->{Home} = $Self->{ConfigObject}->Get('Home');
# set utf8 if used
$Self->{utf8} = 1;
$Self->{FileMode} = ':utf8';
$Self->{ConfigDefaultObject} = Kernel::Config->new( Level => 'Default' );
$Self->{ConfigObject} = Kernel::Config->new( Level => 'First' );
$Self->{ConfigClearObject} = Kernel::Config->new( Level => 'Clear' );
# Load base files.
my $BaseDir = $Self->{Home} . '/Kernel/System/SysConfig/Base/';
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
FILENAME:
for my $Filename (qw(Framework.pm OTRSBusiness.pm)) {
my $BaseFile = $BaseDir . $Filename;
next FILENAME if !-e $BaseFile;
$BaseFile =~ s{\A.*\/(.+?).pm\z}{$1}xms;
my $BaseClassName = "Kernel::System::SysConfig::Base::$BaseFile";
if ( !$MainObject->RequireBaseClass($BaseClassName) ) {
$Self->FatalDie(
Message => "Could not load class $BaseClassName.",
);
}
}
return $Self;
}
=head2 SettingGet()
Get SysConfig setting attributes.
my %Setting = $SysConfigObject->SettingGet(
Name => 'Setting::Name', # (required) Setting name
Default => 1, # (optional) Returns the default setting attributes only
ModifiedID => '123', # (optional) Get setting value for given ModifiedID.
TargetUserID => 1, # (optional) Get setting value for specific user.
Deployed => 1, # (optional) Get deployed setting value. Default 0.
OverriddenInXML => 1, # (optional) Consider changes made in perl files. Default 0.
Translate => 1, # (optional) Translate translatable strings in EffectiveValue. Default 0.
NoLog => 1, # (optional) Do not log error if a setting does not exist.
NoCache => 1, # (optional) Do not create cache.
UserID => 1, # Required only if OverriddenInXML is set.
);
Returns:
%Setting = (
DefaultID => 123,
ModifiedID => 456, # optional
Name => "ProductName",
Description => "Defines the name of the application ...",
Navigation => "ASimple::Path::Structure",
IsInvisible => 1, # 1 or 0
IsReadonly => 0, # 1 or 0
IsRequired => 1, # 1 or 0
IsModified => 1, # 1 or 0
IsValid => 1, # 1 or 0
HasConfigLevel => 200,
UserModificationPossible => 0, # 1 or 0
UserModificationActive => 0, # 1 or 0
UserPreferencesGroup => 'Advanced', # optional
XMLContentRaw => "The XML structure as it is on the config file",
XMLContentParsed => "XML parsed to Perl",
XMLFilename => "Framework.xml",
EffectiveValue => "Product 6",
IsDirty => 1, # 1 or 0
ExclusiveLockGUID => 'A32CHARACTERLONGSTRINGFORLOCKING',
ExclusiveLockUserID => 1,
ExclusiveLockExpiryTime => '2016-05-29 11:09:04',
CreateTime => "2016-05-29 11:04:04",
CreateBy => 1,
ChangeTime => "2016-05-29 11:04:04",
ChangeBy => 1,
DefaultValue => 'Old default value',
OverriddenFileName => '/opt/otrs/Kernel/Config/Files/ZZZ.pm',
);
=cut
sub SettingGet {
my ( $Self, %Param ) = @_;
# Check needed stuff.
if ( !$Param{Name} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need Name!',
);
return;
}
if ( $Param{OverriddenInXML} && !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'UserID is needed when OverriddenInXML is set!',
);
return;
}
$Param{Translate} //= 0; # don't translate by default
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
# Get default setting.
my %Setting = $SysConfigDBObject->DefaultSettingGet(
Name => $Param{Name},
NoCache => $Param{NoCache},
);
# setting was not found
if ( !%Setting ) {
# do not log an error if parameter NoLog is true
if ( !$Param{NoLog} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Setting $Param{Name} is invalid!",
);
}
return;
}
$Setting{DefaultValue} = $Setting{EffectiveValue};
# Return default setting if specified (otherwise continue with modified setting).
if ( $Param{Default} ) {
return %Setting;
}
# Check if modified setting available
my %ModifiedSetting;
if ( $Param{ModifiedID} ) {
# Get settings with given ModifiedID.
%ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
ModifiedID => $Param{ModifiedID},
IsGlobal => 1,
NoCache => $Param{NoCache},
);
# prevent using both parameters.
$Param{Deployed} = undef;
$Param{TargetUserID} = undef;
}
else {
# Get latest modified setting.
%ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
Name => $Param{Name},
IsGlobal => 1,
NoCache => $Param{NoCache},
);
}
if ( $Param{TargetUserID} ) {
if ( IsHashRefWithData( \%ModifiedSetting ) ) {
# There is modified setting, but we need last deployed version.
%ModifiedSetting = $SysConfigDBObject->ModifiedSettingVersionGetLast(
Name => $ModifiedSetting{Name},
);
# Use global (deployed) modified settings as "default" (if any)
if ( IsHashRefWithData( \%ModifiedSetting ) ) {
%Setting = (
%Setting,
%ModifiedSetting,
);
}
}
# get user specific settings
%ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
Name => $Param{Name},
TargetUserID => $Param{TargetUserID},
);
# prevent using both parameters.
$Param{Deployed} = undef;
}
if ( $Param{Deployed} ) {
# get the previous deployed state of this setting
my %SettingDeployed = $SysConfigDBObject->ModifiedSettingVersionGetLast(
Name => $Setting{Name},
);
if ( !IsHashRefWithData( \%SettingDeployed ) ) {
# if this setting was never deployed before, get the default state
# Get default version.
%SettingDeployed = $SysConfigDBObject->DefaultSettingGet(
DefaultID => $Setting{DefaultID},
NoCache => $Param{NoCache},
);
}
if ( IsHashRefWithData( \%SettingDeployed ) ) {
%Setting = (
%Setting,
%SettingDeployed
);
}
}
# default
$Setting{IsModified} = 0;
if ( IsHashRefWithData( \%ModifiedSetting ) ) {
my $IsModified = DataIsDifferent(
Data1 => \$Setting{EffectiveValue},
Data2 => \$ModifiedSetting{EffectiveValue},
) || 0;
$IsModified ||= $ModifiedSetting{IsValid} != $Setting{IsValid};
$IsModified ||= $ModifiedSetting{UserModificationActive} != $Setting{UserModificationActive};
$Setting{IsModified} = $IsModified ? 1 : 0;
if ( !$Param{Deployed} ) {
# Update setting attributes.
ATTRIBUTE:
for my $Attribute (
qw(ModifiedID IsValid UserModificationActive EffectiveValue IsDirty
CreateTime CreateBy ChangeTime ChangeBy SettingUID
)
)
{
next ATTRIBUTE if !defined $ModifiedSetting{$Attribute};
$Setting{$Attribute} = $ModifiedSetting{$Attribute};
}
}
}
if ( $Param{OverriddenInXML} ) {
# get the previous deployed state of this setting
my %SettingDeployed = $SysConfigDBObject->ModifiedSettingVersionGetLast(
Name => $Setting{Name},
);
if ( !IsHashRefWithData( \%SettingDeployed ) ) {
# if this setting was never deployed before, get the default state
# Get default version.
%SettingDeployed = $SysConfigDBObject->DefaultSettingGet(
DefaultID => $Setting{DefaultID},
NoCache => $Param{NoCache},
);
}
# Get real EffectiveValue - EffectiveValue from DB could be modified in the ZZZAbc.pm file.
my $LoadedEffectiveValue = $Self->GlobalEffectiveValueGet(
SettingName => $Setting{Name},
);
my $IsOverridden = DataIsDifferent(
Data1 => $SettingDeployed{EffectiveValue} // {},
Data2 => $LoadedEffectiveValue // {},
);
if ($IsOverridden) {
$Setting{OverriddenFileName} = $Self->OverriddenFileNameGet(
SettingName => $Setting{Name},
EffectiveValue => $Setting{EffectiveValue},
UserID => $Param{UserID},
);
# Update EffectiveValue.
if ( $Setting{OverriddenFileName} ) {
$Setting{EffectiveValue} = $LoadedEffectiveValue;
}
}
}
if ( $Param{Translate} ) {
if (%ModifiedSetting) {
$Setting{XMLContentParsed}->{Value} = $Self->SettingModifiedXMLContentParsedGet(
ModifiedSetting => {
EffectiveValue => $Setting{EffectiveValue},
},
DefaultSetting => {
XMLContentParsed => $Setting{XMLContentParsed},
},
);
}
# Update EffectiveValue with translated strings
$Setting{EffectiveValue} = $Self->SettingEffectiveValueGet(
Value => $Setting{XMLContentParsed}->{Value},
Translate => 1,
);
$Setting{Description} = $Kernel::OM->Get('Kernel::Language')->Translate(
$Setting{Description},
);
}
# If setting is overridden in the perl file, using the "delete" statement, EffectiveValue is undef.
$Setting{EffectiveValue} //= '';
# Return updated default.
return %Setting;
}
=head2 SettingUpdate()
Update an existing SysConfig Setting.
my %Result = $SysConfigObject->SettingUpdate(
Name => 'Setting::Name', # (required) setting name
IsValid => 1, # (optional) 1 or 0, modified 0
EffectiveValue => $SettingEffectiveValue, # (optional)
UserModificationActive => 0, # (optional) 1 or 0, modified 0
TargetUserID => 2, # (optional) ID of the user for which the modified setting is meant,
# leave it undef for global changes.
ExclusiveLockGUID => $LockingString, # the GUID used to locking the setting
UserID => 1, # (required) UserID
NoValidation => 1, # (optional) no value type validation.
);
Returns:
%Result = (
Success => 1, # or false in case of an error
Error => undef, # error message
);
=cut
sub SettingUpdate {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Name UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
if ( !$Param{TargetUserID} && !$Param{ExclusiveLockGUID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need TargetUserID or ExclusiveLockGUID!",
);
}
my %Result = (
Success => 1,
);
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
# Get default setting
my %Setting = $SysConfigDBObject->DefaultSettingGet(
Name => $Param{Name},
);
# Make sure that required settings can't be disabled.
if ( $Setting{IsRequired} ) {
$Param{IsValid} = 1;
}
# Return if setting does not exists.
if ( !%Setting ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Setting $Param{Name} does not exists!",
);
%Result = (
Success => 0,
Error => $Kernel::OM->Get('Kernel::Language')->Translate(
"Setting %s does not exists!",
$Param{Name},
),
);
return %Result;
}
# Default should be locked (for global updates).
my $LockedByUser;
if ( !$Param{TargetUserID} ) {
$LockedByUser = $SysConfigDBObject->DefaultSettingIsLockedByUser(
DefaultID => $Setting{DefaultID},
ExclusiveLockUserID => $Param{UserID},
ExclusiveLockGUID => $Param{ExclusiveLockGUID},
);
if ( !$LockedByUser ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Setting $Param{Name} is not locked to this user!",
);
%Result = (
Success => 0,
Error => $Kernel::OM->Get('Kernel::Language')->Translate(
"Setting %s is not locked to this user!",
$Param{Name},
),
);
return %Result;
}
}
# Do not perform EffectiveValueCheck if user wants to disable the setting.
if ( $Param{IsValid} ) {
# Effective value must match in structure to the default and individual values should be
# valid according to its value types.
my %EffectiveValueCheck = $Self->SettingEffectiveValueCheck(
XMLContentParsed => $Setting{XMLContentParsed},
EffectiveValue => $Param{EffectiveValue},
NoValidation => $Param{NoValidation} //= 0,
UserID => $Param{UserID},
);
if ( !$EffectiveValueCheck{Success} ) {
my $Error = $EffectiveValueCheck{Error} || 'Unknown error!';
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "EffectiveValue is invalid! $Error",
);
%Result = (
Success => 0,
Error => $Kernel::OM->Get('Kernel::Language')->Translate(
"Setting value is not valid!",
),
);
return %Result;
}
}
# Get modified setting (if any).
my %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
Name => $Param{Name},
IsGlobal => 1,
);
if ( !defined $Param{EffectiveValue} ) {
# In the case that we want only to enable/disable setting,
# old effective value will be preserved.
$Param{EffectiveValue} = $ModifiedSetting{EffectiveValue} // $Setting{EffectiveValue};
}
my $UserModificationActive = $Param{UserModificationActive} //= $Setting{UserModificationActive};
if ( $Param{TargetUserID} ) {
if ( IsHashRefWithData( \%ModifiedSetting ) ) {
# override default setting with global modified setting
%Setting = (
%Setting,
%ModifiedSetting,
);
}
%ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
Name => $Param{Name},
TargetUserID => $Param{TargetUserID},
);
$UserModificationActive = undef; # prevent setting this value
my %GlobalSetting = $Self->SettingGet(
Name => $Param{Name},
OverriddenInXML => 1,
UserID => 1,
);
$Setting{EffectiveValue} = $GlobalSetting{EffectiveValue};
}
# Add new modified setting (if there wasn't).
if ( !%ModifiedSetting ) {
# Check if provided EffectiveValue is same as in Default
my $IsDifferent = DataIsDifferent(
Data1 => \$Setting{EffectiveValue},
Data2 => \$Param{EffectiveValue},
) || 0;
if ( defined $Param{IsValid} ) {
$IsDifferent ||= $Setting{IsValid} != $Param{IsValid};
}
$IsDifferent ||= $Setting{UserModificationActive} != $Param{UserModificationActive};
if ($IsDifferent) {
my $ModifiedID = $SysConfigDBObject->ModifiedSettingAdd(
DefaultID => $Setting{DefaultID},
Name => $Setting{Name},
IsValid => $Param{IsValid} //= $Setting{IsValid},
EffectiveValue => $Param{EffectiveValue},
UserModificationActive => $UserModificationActive,
TargetUserID => $Param{TargetUserID},
ExclusiveLockGUID => $Param{ExclusiveLockGUID},
UserID => $Param{UserID},
);
if ( !$ModifiedID ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not add modified setting!",
);
%Result = (
Success => 0,
Error => $Kernel::OM->Get('Kernel::Language')->Translate(
"Could not add modified setting!",
),
);
return %Result;
}
}
}
else {
# Check if provided EffectiveValue is same as in last modified EffectiveValue
my $IsDifferent = DataIsDifferent(
Data1 => \$ModifiedSetting{EffectiveValue},
Data2 => \$Param{EffectiveValue},
) || 0;
if ( defined $Param{IsValid} ) {
$IsDifferent ||= $ModifiedSetting{IsValid} != $Param{IsValid};
}
$IsDifferent ||= $ModifiedSetting{UserModificationActive} != $Param{UserModificationActive};
if ($IsDifferent) {
my %ModifiedSettingVersion = $SysConfigDBObject->ModifiedSettingVersionGetLast(
Name => $ModifiedSetting{Name},
);
my $EffectiveValueModifiedSinceDeployment = 1;
if ( $ModifiedSettingVersion{ModifiedVersionID} ) {
my %ModifiedSettingLastDeployed = $SysConfigDBObject->ModifiedSettingVersionGet(
ModifiedVersionID => $ModifiedSettingVersion{ModifiedVersionID},
);
$EffectiveValueModifiedSinceDeployment = DataIsDifferent(
Data1 => \$ModifiedSettingLastDeployed{EffectiveValue},
Data2 => \$Param{EffectiveValue},
) || 0;
if ( defined $Param{IsValid} ) {
$EffectiveValueModifiedSinceDeployment ||= $ModifiedSettingLastDeployed{IsValid} != $Param{IsValid};
}
$EffectiveValueModifiedSinceDeployment
||= $ModifiedSettingLastDeployed{UserModificationActive} != $Param{UserModificationActive};
}
elsif ( !IsHashRefWithData( \%ModifiedSettingVersion ) ) {
$EffectiveValueModifiedSinceDeployment = DataIsDifferent(
Data1 => \$Setting{EffectiveValue},
Data2 => \$Param{EffectiveValue},
) || 0;
if ( defined $Param{IsValid} ) {
$EffectiveValueModifiedSinceDeployment ||= $Setting{IsValid} != $Param{IsValid};
}
$EffectiveValueModifiedSinceDeployment
||= $Setting{UserModificationActive} != $Param{UserModificationActive};
}
# Update the existing modified setting.
my $Success = $SysConfigDBObject->ModifiedSettingUpdate(
ModifiedID => $ModifiedSetting{ModifiedID},
DefaultID => $Setting{DefaultID},
Name => $Setting{Name},
IsValid => $Param{IsValid} //= $ModifiedSetting{IsValid},
EffectiveValue => $Param{EffectiveValue},
UserModificationActive => $UserModificationActive,
TargetUserID => $Param{TargetUserID} //= $ModifiedSetting{TargetUserID},
ExclusiveLockGUID => $Param{ExclusiveLockGUID},
UserID => $Param{UserID},
IsDirty => $EffectiveValueModifiedSinceDeployment ? 1 : 0,
);
if ( !$Success ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not update modified setting!",
);
%Result = (
Success => 0,
Error => $Kernel::OM->Get('Kernel::Language')->Translate(
"Could not update modified setting!",
),
);
return %Result;
}
}
}
# When a setting is set to invalid all modified settings for users has to be removed.
if (
!$Param{IsValid}
&& !$Param{TargetUserID}
&& $Self->can('UserSettingValueDelete') # OTRS Business Solution™
)
{
$Self->UserSettingValueDelete(
Name => $Setting{Name},
ModifiedID => 'All',
UserID => $Param{UserID},
);
}
if ( !$Param{TargetUserID} ) {
# Unlock setting so it can be locked again afterwards.
my $Success = $SysConfigDBObject->DefaultSettingUnlock(
DefaultID => $Setting{DefaultID},
);
if ( !$Success ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Setting could not be unlocked!",
);
%Result = (
Success => 0,
Error => $Kernel::OM->Get('Kernel::Language')->Translate(
"Setting could not be unlocked!",
),
);
return %Result;
}
}
return %Result;
}
=head2 SettingLock()
Lock setting(s) to the particular user.
my $ExclusiveLockGUID = $SysConfigObject->SettingLock(
DefaultID => 1, # the ID of the setting that needs to be locked
# or
Name => 'SettingName', # the Name of the setting that needs to be locked
# or
LockAll => 1, # system locks all settings
Force => 1, # (optional) Force locking (do not check if it's already locked by another user). Default: 0.
UserID => 1, # (required)
);
Returns:
$ExclusiveLockGUID = 'azzHab72wIlAXDrxHexsI5aENsESxAO7'; # Setting locked
or
$ExclusiveLockGUID = undef; # Not locked
=cut
sub SettingLock {
my ( $Self, %Param ) = @_;
return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingLock(%Param);
}
=head2 SettingUnlock()
Unlock particular or all Setting(s).
my $Success = $SysConfigObject->SettingUnlock(
DefaultID => 1, # the ID of the setting that needs to be unlocked
# or
Name => 'SettingName', # the name of the setting that needs to be locked
# or
UnlockAll => 1, # unlock all settings
);
Returns:
$Success = 1;
=cut
sub SettingUnlock {
my ( $Self, %Param ) = @_;
return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingUnlock(%Param);
}
=head2 SettingLockCheck()
Check setting lock status.
my %Result = $SysConfigObject->SettingLockCheck(
DefaultID => 1, # the ID of the setting that needs to be checked
ExclusiveLockGUID => 1, # lock GUID
ExclusiveLockUserID => 1, # UserID
);
Returns:
%Result = (
Locked => 1, # lock status;
# 0 - unlocked
# 1 - locked to another user
# 2 - locked to provided user
User => { # User data, provided only if Locked = 1
UserLogin => ...,
UserFirstname => ...,
UserLastname => ...,
...
},
);
=cut
sub SettingLockCheck {
my ( $Self, %Param ) = @_;
for my $Needed (qw(DefaultID ExclusiveLockGUID ExclusiveLockUserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my %Result = (
Locked => 0,
);
my $LockedByUser = $SysConfigDBObject->DefaultSettingIsLockedByUser(
DefaultID => $Param{DefaultID},
ExclusiveLockUserID => $Param{ExclusiveLockUserID},
ExclusiveLockGUID => $Param{ExclusiveLockGUID},
);
if ($LockedByUser) {
# setting locked to the provided user
$Result{Locked} = 2;
}
else {
# check if setting is locked to another user
my $UserID = $SysConfigDBObject->DefaultSettingIsLocked(
DefaultID => $Param{DefaultID},
GetLockUserID => 1,
);
if ($UserID) {
# get user data
$Result{Locked} = 1;
my %User = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
UserID => $UserID,
);
$Result{User} = \%User;
}
}
return %Result;
}
=head2 SettingEffectiveValueGet()
Calculate effective value for a given parsed XML structure.
my $Result = $SysConfigObject->SettingEffectiveValueGet(
Translate => 1, # (optional) Translate translatable strings. Default 0.
Value => [ # (required) parsed XML structure
{
'Item' => [
{
'ValueType' => 'String',
'Content' => '3600',
'ValueRegex' => ''
},
],
},
Objects => {
Select => { ... },
PerlModule => { ... },
# ...
}
];
);
Returns:
$Result = '3600';
=cut
sub SettingEffectiveValueGet {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Value)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my %ForbiddenValueTypes = %{ $Self->{ForbiddenValueTypes} || {} };
if ( !%ForbiddenValueTypes ) {
%ForbiddenValueTypes = $Self->ForbiddenValueTypesGet();
$Self->{ForbiddenValueTypes} = \%ForbiddenValueTypes;
}
$Param{Translate} //= 0;
my $Result;
my %Objects;
if ( $Param{Objects} ) {
%Objects = %{ $Param{Objects} };
}
# Make sure structure is correct.
return $Result if !IsArrayRefWithData( $Param{Value} );
return $Result if !IsHashRefWithData( $Param{Value}->[0] );
my %Attributes;
if ( $Param{Value}->[0]->{Item} ) {
# Make sure structure is correct.
return $Result if !IsArrayRefWithData( $Param{Value}->[0]->{Item} );
return $Result if !IsHashRefWithData( $Param{Value}->[0]->{Item}->[0] );
# Set default ValueType.
my $ValueType = "String";
if ( $Param{Value}->[0]->{Item}->[0]->{ValueType} ) {
$ValueType = $Param{Value}->[0]->{Item}->[0]->{ValueType};
}
if ( !$Objects{$ValueType} ) {
# Make sure value type backend is available and syntactically correct.
my $Loaded = $Kernel::OM->Get('Kernel::System::Main')->Require(
"Kernel::System::SysConfig::ValueType::$ValueType",
);
return $Result if !$Loaded;
$Objects{$ValueType} = $Kernel::OM->Get(
"Kernel::System::SysConfig::ValueType::$ValueType",
);
}
# Create a local clone of the value to prevent any modification.
my $Value = $Kernel::OM->Get('Kernel::System::Storable')->Clone(
Data => $Param{Value}->[0]->{Item},
);
$Result = $Objects{$ValueType}->EffectiveValueGet(
Value => $Value,
Translate => $Param{Translate},
);
}
elsif ( $Param{Value}->[0]->{Hash} ) {
# Make sure structure is correct.
return {} if !IsArrayRefWithData( $Param{Value}->[0]->{Hash} );
return {} if !IsHashRefWithData( $Param{Value}->[0]->{Hash}->[0] );
return {} if !IsArrayRefWithData( $Param{Value}->[0]->{Hash}->[0]->{Item} );
# Check for additional attributes in the DefaultItem.
if (
$Param{Value}->[0]->{Hash}->[0]->{DefaultItem}
&& ref $Param{Value}->[0]->{Hash}->[0]->{DefaultItem} eq 'ARRAY'
&& scalar $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}
&& ref $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0] eq 'HASH'
)
{
%Attributes = ();
my @ValueAttributeList = $Self->ValueAttributeList();
ATTRIBUTE:
for my $Attribute ( sort keys %{ $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0] } ) {
if (
( grep { $_ eq $Attribute } qw (Array Hash) )
&& $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$Attribute}->[0]->{DefaultItem}
)
{
$Attributes{DefaultItem}
= $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$Attribute}->[0]->{DefaultItem};
}
next ATTRIBUTE if grep { $Attribute eq $_ } ( qw (Array Hash), @ValueAttributeList );
if (
$Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{Item}
&& $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{ValueType}
)
{
my $DefaultItemValueType = $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{ValueType};
if ( $ForbiddenValueTypes{$DefaultItemValueType} ) {
my $SubValueType
= $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{Item}->[0]->{ValueType};
if ( !grep { $_ eq $SubValueType } @{ $ForbiddenValueTypes{$DefaultItemValueType} } ) {
next ATTRIBUTE;
}
}
}
$Attributes{$Attribute} = $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$Attribute};
}
}
ITEM:
for my $Item ( @{ $Param{Value}->[0]->{Hash}->[0]->{Item} } ) {
next ITEM if !IsHashRefWithData($Item);
next ITEM if !defined $Item->{Key};
if ( $Item->{Hash} || $Item->{Array} ) {
my $ItemKey = $Item->{Hash} ? 'Hash' : 'Array';
ATTRIBUTE:
for my $Attribute ( sort keys %Attributes ) {
next ATTRIBUTE if defined $Item->{$Attribute}; # skip redefined values
$Item->{$ItemKey}->[0]->{$Attribute} = $Attributes{$Attribute};
}
my $Value = $Self->SettingEffectiveValueGet(
Value => [$Item],
Objects => \%Objects,
Translate => $Param{Translate},
);
$Result->{ $Item->{Key} } = $Value;
}
elsif ( $Attributes{ValueType} || $Item->{ValueType} ) {
# Create a local clone of the item to prevent any modification.
my $Clone = $Kernel::OM->Get('Kernel::System::Storable')->Clone(
Data => $Item,
);
ATTRIBUTE:
for my $Attribute ( sort keys %Attributes ) {
next ATTRIBUTE if defined $Clone->{$Attribute}; # skip redefined values
$Clone->{$Attribute} = $Attributes{$Attribute};
}
my $Value = $Self->SettingEffectiveValueGet(
Value => [
{
Item => [$Clone],
},
],
Objects => \%Objects,
Translate => $Param{Translate},
);
$Result->{ $Item->{Key} } = $Value;
}
else {
$Item->{Content} //= '';
# Remove empty space at start and the end (with new lines).
$Item->{Content} =~ s{^\n\s*(.*?)\n\s*$}{$1}gsmx;
$Result->{ $Item->{Key} } = $Item->{Content};
}
}
}
elsif ( $Param{Value}->[0]->{Array} ) {
# Make sure structure is correct
return [] if !IsArrayRefWithData( $Param{Value}->[0]->{Array} );
return [] if !IsHashRefWithData( $Param{Value}->[0]->{Array}->[0] );
return [] if !IsArrayRefWithData( $Param{Value}->[0]->{Array}->[0]->{Item} );
# Check for additional attributes in the DefaultItem.
if (
$Param{Value}->[0]->{Array}->[0]->{DefaultItem}
&& ref $Param{Value}->[0]->{Array}->[0]->{DefaultItem} eq 'ARRAY'
&& scalar $Param{Value}->[0]->{Array}->[0]->{DefaultItem}
&& ref $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0] eq 'HASH'
)
{
%Attributes = ();
ATTRIBUTE:
for my $Attribute ( sort keys %{ $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0] } ) {
if (
( grep { $_ eq $Attribute } qw (Array Hash) )
&& $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0]->{$Attribute}->[0]->{DefaultItem}
)
{
$Attributes{DefaultItem}
= $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0]->{$Attribute}->[0]->{DefaultItem};
}
next ATTRIBUTE if grep { $Attribute eq $_ } qw (Array Hash Content SelectedID);
$Attributes{$Attribute} = $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0]->{$Attribute};
}
}
my @Items;
ITEM:
for my $Item ( @{ $Param{Value}->[0]->{Array}->[0]->{Item} } ) {
next ITEM if !IsHashRefWithData($Item);
if ( $Item->{Hash} || $Item->{Array} ) {
my $ItemKey = $Item->{Hash} ? 'Hash' : 'Array';
ATTRIBUTE:
for my $Attribute ( sort keys %Attributes ) {
next ATTRIBUTE if defined $Item->{$Attribute}; # skip redefined values
$Item->{$ItemKey}->[0]->{$Attribute} = $Attributes{$Attribute};
}
my $Value = $Self->SettingEffectiveValueGet(
Value => [$Item],
Objects => \%Objects,
Translate => $Param{Translate},
);
push @Items, $Value;
}
elsif ( $Attributes{ValueType} ) {
# Create a local clone of the item to prevent any modification.
my $Clone = $Kernel::OM->Get('Kernel::System::Storable')->Clone(
Data => $Item,
);
ATTRIBUTE:
for my $Attribute ( sort keys %Attributes ) {
next ATTRIBUTE if defined $Clone->{$Attribute}; # skip redefined values
$Clone->{$Attribute} = $Attributes{$Attribute};
}
my $Value = $Self->SettingEffectiveValueGet(
Value => [
{
Item => [$Clone],
},
],
Objects => \%Objects,
Translate => $Param{Translate},
);
push @Items, $Value;
}
else {
$Item->{Content} //= '';
# Remove empty space at start and the end (with new lines).
$Item->{Content} =~ s{^\n\s*(.*?)\n\s*$}{$1}gsmx;
push @Items, $Item->{Content};
}
}
$Result = \@Items;
}
return $Result;
}
=head2 SettingRender()
Wrapper for Kernel::Output::HTML::SysConfig::SettingRender() - Returns the specific HTML for the setting.
my $HTMLStr = $SysConfigObject->SettingRender(
Setting => {
Name => 'Setting Name',
XMLContentParsed => $XMLParsedToPerl,
EffectiveValue => "Product 6", # or a complex structure
DefaultValue => "Product 5", # or a complex structure
IsAjax => 1, # (optional) is ajax request. Default 0.
# ...
},
RW => 1, # (optional) Allow editing. Default 0.
);
Returns:
$HTMLStr = '<div class="Setting"><div class "Field"...</div></div>' # or false in case of an error
=cut
sub SettingRender {
my ( $Self, %Param ) = @_;
return $Kernel::OM->Get('Kernel::Output::HTML::SysConfig')->SettingRender(%Param);
}
=head2 SettingAddItem()
Wrapper for Kernel::Output::HTML::SysConfig::SettingAddItem() - Returns response that is sent when user adds new array/hash item.
my %Result = $SysConfigObject->SettingAddItem(
SettingStructure => [], # (required) array that contains structure
# where a new item should be inserted (can be empty)
Setting => { # (required) Setting hash (from SettingGet())
'DefaultID' => '8905',
'DefaultValue' => [ 'Item 1', 'Item 2' ],
'Description' => 'Simple array item(Min 1, Max 3).',
'Name' => 'TestArray',
...
},
Key => 'HashKey', # (optional) hash key
IDSuffix => '_Array3, # (optional) suffix that will be added to all input/select fields
# (it is used in the JS on Update, during EffectiveValue calculation)
Value => [ # (optional) Perl structure
{
'Array' => [
'Item' => [
{
'Content' => 'Item 1',
},
...
],
],
},
],
AddSettingContent => 0, # (optional) if enabled, result will be inside of div with class "SettingContent"
);
Returns:
%Result = (
'Item' => '<div class=\'SettingContent\'>
<input type=\'text\' id=\'TestArray_Array4\'
value=\'Default value\' name=\'TestArray\' class=\' Entry\'/></div>',
);
or
%Result = (
'Error' => 'Error description',
);
=cut
sub SettingAddItem {
my ( $Self, %Param ) = @_;
return $Kernel::OM->Get('Kernel::Output::HTML::SysConfig')->SettingAddItem(%Param);
}
=head2 SettingsUpdatedList()
Checks which settings has been updated from provided Setting list and returns updated values.
my @List = $SysConfigObject->SettingsUpdatedList(
Settings => [ # (required) List of settings that needs to be checked
{
SettingName => 'SettingName',
ChangeTime => '2017-01-13 11:23:07',
IsLockedByAnotherUser => 0,
},
...
],
UserID => 1, # (required) Current user id
);
Returns:
@List = [
{
ChangeTime => '2017-01-07 11:29:38',
IsLockedByAnotherUser => 1,
IsModified => 1,
SettingName => 'SettingName',
},
...
];
=cut
sub SettingsUpdatedList {
my ( $Self, %Param ) = @_;
# Check needed stuff.
for my $Needed (qw(Settings UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my @Result;
SETTING:
for my $Setting ( @{ $Param{Settings} } ) {
next SETTING if !IsHashRefWithData($Setting);
my %SettingData = $Self->SettingGet(
Name => $Setting->{SettingName},
);
my $LockedUserID = $SysConfigDBObject->DefaultSettingIsLocked(
Name => $Setting->{SettingName},
GetLockUserID => 1,
);
my $IsLockedByAnotherUser = $LockedUserID ? 1 : 0;
# Skip if setting was locked by current user during AJAX call.
next SETTING if $LockedUserID == $Param{UserID};
my $Updated = $SettingData{ChangeTime} ne $Setting->{ChangeTime};
$Updated ||= $IsLockedByAnotherUser != $Setting->{IsLockedByAnotherUser};
$Setting->{IsLockedByAnotherUser} = $IsLockedByAnotherUser;
next SETTING if !$Updated;
push @Result, $Setting;
}
return @Result;
}
=head2 SettingEffectiveValueCheck()
Check if provided EffectiveValue matches structure defined in DefaultSetting. Also returns EffectiveValue that might be changed.
my %Result = $SysConfigObject->SettingEffectiveValueCheck(
EffectiveValue => 'open', # (optional)
XMLContentParsed => { # (required)
Value => [
{
'Item' => [
{
'Content' => "Scalar value",
},
],
},
],
},
StoreCache => 1, # (optional) Store result in the Cache. Default 0.
SettingUID => 'Default1234' # (required if StoreCache)
NoValidation => 1, # (optional) no value type validation.
CurrentSystemTime => 1507894796935, # (optional) Use provided 1507894796935, otherwise calculate
ExpireTime => 1507894896935, # (optional) Use provided ExpireTime for cache, otherwise calculate
UserID => 1, # (required) UserID
);
Returns:
%Result = (
EffectiveValue => 'closed', # Note that resulting effective value can be different
Success => 1,
Error => undef,
);
=cut
sub SettingEffectiveValueCheck {
my ( $Self, %Param ) = @_;
for my $Needed (qw(XMLContentParsed UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!"
);
return;
}
}
$Param{EffectiveValue} //= '';
$Param{NoValidation} //= 0;
my $StoreCache = $Param{StoreCache};
if ( $Param{StoreCache} && !$Param{SettingUID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "SettingEffectiveValueCheck() called with StoreCache but without SettingUID parameter!"
);
$StoreCache = 0; # Fallback, do not use cache.
}
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $CacheType = 'SysConfigPersistent';
my $CacheKey = "EffectiveValueCheck::$Param{NoValidation}";
my $SettingKey;
my $Cache;
my $DateTimeObject;
my $CurrentSystemTime = $Param{CurrentSystemTime};
# Get current system time, if not provided.
if ( !$CurrentSystemTime ) {
$DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
$CurrentSystemTime = $DateTimeObject->ToEpoch();
}
my $ExpireTime = $Param{ExpireTime};
# Get cache expire time, if not provided.
if ( !$ExpireTime ) {
if ( !$DateTimeObject ) {
$DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
}
# Set expire date.
$DateTimeObject->Add(
Months => 1,
);
$ExpireTime = $DateTimeObject->ToEpoch();
}
if ( $Param{SettingUID} ) {
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
my $ValueString = $Param{EffectiveValue};
if ( ref $ValueString ) {
my $String = $StorableObject->Serialize(
Data => $Param{EffectiveValue},
);
$ValueString = $MainObject->MD5sum(
String => \$String,
);
}
$SettingKey = "$Param{SettingUID}::${ValueString}";
$Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
if ( $Cache && !$Self->{EffectiveValueCheckCacheDeleted} ) {
# Delete all expired keys.
my @ExpiredKeys = grep { $CurrentSystemTime > ( $Cache->{$_}->{ExpireTime} || 0 ) } keys %{$Cache};
delete @{$Cache}{@ExpiredKeys};
if (@ExpiredKeys) {
# Update cache.
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => $Cache,
TTL => 20 * 24 * 60 * 60,
);
}
# Remember delete in this round
$Self->{EffectiveValueCheckCacheDeleted} = 1;
}
if (
ref $Cache eq 'HASH'
&& $Cache->{$SettingKey}
)
{
return %{ $Cache->{$SettingKey} };
}
}
my %Result = (
Success => 0,
);
my %Parameters = %{ $Param{Parameters} || {} };
my $Value = $Param{XMLContentParsed}->{Value};
if ( $Value->[0]->{Item} || $Value->[0]->{ValueType} ) {
# get ValueType from parent or use default
my $ValueType = $Parameters{ValueType} || $Value->[0]->{ValueType} || 'String';
# ValueType is defined explicitly(override parent definition)
if (
$Value->[0]->{Item}
&& $Value->[0]->{Item}->[0]->{ValueType}
)
{
$ValueType = $Value->[0]->{Item}->[0]->{ValueType};
}
my %ForbiddenValueTypes = %{ $Self->{ForbiddenValueTypes} || {} };
if ( !%ForbiddenValueTypes ) {
%ForbiddenValueTypes = $Self->ForbiddenValueTypesGet();
$Self->{ForbiddenValueTypes} = \%ForbiddenValueTypes;
}
my @SkipValueTypes;
for my $Item ( sort keys %ForbiddenValueTypes ) {
if ( !grep { $_ eq $ForbiddenValueTypes{$Item} } @SkipValueTypes ) {
push @SkipValueTypes, @{ $ForbiddenValueTypes{$Item} };
}
}
if (
$Param{NoValidation}
|| grep { $_ eq $ValueType } @SkipValueTypes
)
{
$Result{Success} = 1;
$Result{EffectiveValue} = $Param{EffectiveValue};
return %Result;
}
my $BackendObject = $Self->{ValueTypeBackendObject}->{$ValueType} || '';
if ( !$BackendObject ) {
my $Loaded = $Kernel::OM->Get('Kernel::System::Main')->Require(
"Kernel::System::SysConfig::ValueType::$ValueType",
);
if ( !$Loaded ) {
$Result{Error} = "Kernel::System::SysConfig::ValueType::$ValueType";
return %Result;
}
$BackendObject = $Kernel::OM->Get(
"Kernel::System::SysConfig::ValueType::$ValueType",
);
$Self->{ValueTypeBackendObject}->{$ValueType} = $BackendObject;
}
%Result = $BackendObject->SettingEffectiveValueCheck(%Param);
$Param{EffectiveValue} = $Result{EffectiveValue} if $Result{Success};
}
elsif ( $Value->[0]->{Hash} ) {
if ( ref $Param{EffectiveValue} ne 'HASH' ) {
$Result{Error} = 'Its not a hash!';
return %Result;
}
PARAMETER:
for my $Parameter ( sort keys %{ $Value->[0]->{Hash}->[0] } ) {
next PARAMETER if !grep { $_ eq $Parameter } qw(MinItems MaxItems);
$Parameters{$Parameter} = $Value->[0]->{Hash}->[0]->{$Parameter} || '';
}
if ( $Parameters{MinItems} && $Parameters{MinItems} > keys %{ $Param{EffectiveValue} } ) {
$Result{Error} = "Number of items in hash is less than MinItems($Parameters{MinItems})!";
return %Result;
}
if ( $Parameters{MaxItems} && $Parameters{MaxItems} < keys %{ $Param{EffectiveValue} } ) {
$Result{Error} = "Number of items in hash is more than MaxItems($Parameters{MaxItems})!";
return %Result;
}
my @Items = ();
if (
scalar @{ $Value->[0]->{Hash} }
&& $Value->[0]->{Hash}->[0]->{Item}
&& ref $Value->[0]->{Hash}->[0]->{Item} eq 'ARRAY'
)
{
@Items = @{ $Value->[0]->{Hash}->[0]->{Item} };
}
my $DefaultItem;
KEY:
for my $Key ( sort keys %{ $Param{EffectiveValue} } ) {
$DefaultItem = $Value->[0]->{Hash}->[0]->{DefaultItem};
if ( $Value->[0]->{Hash}->[0]->{Item} ) {
my @ItemWithSameKey = grep { $Key eq ( $Value->[0]->{Hash}->[0]->{Item}->[$_]->{Key} || '' ) }
0 .. scalar @{ $Value->[0]->{Hash}->[0]->{Item} };
if ( scalar @ItemWithSameKey ) {
$DefaultItem = [
$Value->[0]->{Hash}->[0]->{Item}->[ $ItemWithSameKey[0] ],
];
}
my $StructureType;
if ( $DefaultItem->[0]->{Array} ) {
$StructureType = 'Array';
}
elsif ( $DefaultItem->[0]->{Hash} ) {
$StructureType = 'Hash';
}
# check if default item is defined in this sub-structure
if (
$StructureType
&& !$DefaultItem->[0]->{$StructureType}->[0]->{DefaultItem}
&& $Value->[0]->{Hash}->[0]->{DefaultItem}
&& $Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$StructureType}
&& $Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$StructureType}->[0]->{DefaultItem}
)
{
# Default Item is not defined here, but it's defined in previous call.
$DefaultItem->[0]->{$StructureType}->[0]->{DefaultItem} =
$Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$StructureType}->[0]->{DefaultItem};
}
}
my $Ref = ref $Param{EffectiveValue}->{$Key};
if ($Ref) {
if ( IsArrayRefWithData($DefaultItem) ) {
my $KeyNeeded;
if ( $Ref eq 'HASH' ) {
$KeyNeeded = 'Hash';
}
elsif ( $Ref eq 'ARRAY' ) {
$KeyNeeded = 'Array';
}
else {
$Result{Error} = "Wrong format!";
last KEY;
}
if ( $DefaultItem->[0]->{Item} ) {
# So far everything is OK, we need to check deeper (recursive).
my %SubResult = $Self->_SettingEffectiveValueCheck(
XMLContentParsed => {
Value => $DefaultItem,
},
EffectiveValue => $Param{EffectiveValue}->{$Key},
NoValidation => $Param{NoValidation},
CurrentSystemTime => $Param{CurrentSystemTime},
ExpireTime => $Param{ExpireTime},
UserID => $Param{UserID},
);
$Param{EffectiveValue}->{$Key} = $SubResult{EffectiveValue} if $SubResult{Success};
if ( $SubResult{Error} ) {
%Result = %SubResult;
last KEY;
}
}
elsif ( !defined $DefaultItem->[0]->{$KeyNeeded} ) {
my $ExpectedText = '';
if ( $DefaultItem->[0]->{Array} ) {
$ExpectedText = "an array reference!";
}
elsif ( $DefaultItem->[0]->{Hash} ) {
$ExpectedText = "a hash reference!";
}
else {
$ExpectedText = "a scalar!";
}
$Result{Error} = "Item with key $Key must be $ExpectedText";
last KEY;
}
else {
# So far everything is OK, we need to check deeper (recursive).
my %SubResult = $Self->_SettingEffectiveValueCheck(
XMLContentParsed => {
Value => $DefaultItem,
},
EffectiveValue => $Param{EffectiveValue}->{$Key},
NoValidation => $Param{NoValidation},
CurrentSystemTime => $Param{CurrentSystemTime},
ExpireTime => $Param{ExpireTime},
UserID => $Param{UserID},
);
$Param{EffectiveValue}->{$Key} = $SubResult{EffectiveValue} if $SubResult{Success};
if ( $SubResult{Error} ) {
%Result = %SubResult;
last KEY;
}
}
}
else {
# Hash is empty in the Defaults, value should be scalar.
$Result{Error} = "Item with key $Key must be a scalar!";
last KEY;
}
}
else {
# scalar value
if ( IsArrayRefWithData($DefaultItem) ) {
if ( $DefaultItem->[0]->{Item} || $DefaultItem->[0]->{Content} ) {
# So far everything is OK, we need to check deeper (recursive).
my %SubResult = $Self->_SettingEffectiveValueCheck(
XMLContentParsed => {
Value => [
{
Item => $DefaultItem,
},
],
},
EffectiveValue => $Param{EffectiveValue}->{$Key},
NoValidation => $Param{NoValidation},
CurrentSystemTime => $Param{CurrentSystemTime},
ExpireTime => $Param{ExpireTime},
UserID => $Param{UserID},
);
if ( $SubResult{Error} ) {
%Result = %SubResult;
last KEY;
}
else {
$Param{EffectiveValue}->{$Key} = $SubResult{EffectiveValue};
}
}
elsif ( $DefaultItem->[0]->{Hash} ) {
$Result{Error} = "Item with key $Key must be a hash reference!";
last KEY;
}
elsif ( $DefaultItem->[0]->{Array} ) {
$Result{Error} = "Item with key $Key must be an array reference!";
last KEY;
}
}
}
}
# Check which Value type is default
my $DefaultValueTypeDefined = 'String';
if (
$Value->[0]->{Hash}->[0]->{DefaultItem}
&& $Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{ValueType}
)
{
$DefaultValueTypeDefined = $Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{ValueType};
}
# Get persistent keys(items with value type different then value type defined in the DefaultItem)
my @PersistentKeys;
for my $Item ( @{ $Value->[0]->{Hash}->[0]->{Item} } ) {
my $ValueType = $DefaultValueTypeDefined;
if ( $Item->{ValueType} ) {
$ValueType = $Item->{ValueType};
}
elsif (
$Item->{Item}
&& $Item->{Item}->[0]->{ValueType}
)
{
$ValueType = $Item->{Item}->[0]->{ValueType};
}
if ( $ValueType ne $DefaultValueTypeDefined && $Item->{Key} ) {
push @PersistentKeys, $Item->{Key};
}
}
# Validate if all persistent keys are present
PERSISTENT_KEY:
for my $Key (@PersistentKeys) {
if ( !defined $Param{EffectiveValue}->{$Key} ) {
$Result{Error} = $Kernel::OM->Get('Kernel::Language')->Translate( "Missing key %s!", $Key );
last PERSISTENT_KEY;
}
}
if ( $Result{Error} ) {
return %Result;
}
$Result{Success} = 1;
}
elsif ( $Value->[0]->{Array} ) {
if ( ref $Param{EffectiveValue} ne 'ARRAY' ) {
$Result{Error} = 'Its not an array!';
return %Result;
}
PARAMETER:
for my $Parameter ( sort keys %{ $Value->[0]->{Array}->[0] } ) {
next PARAMETER if !grep { $_ eq $Parameter } qw(MinItems MaxItems);
$Parameters{$Parameter} = $Value->[0]->{Array}->[0]->{$Parameter} || '';
}
if ( $Parameters{MinItems} && $Parameters{MinItems} > scalar @{ $Param{EffectiveValue} } ) {
$Result{Error} = "Number of items in array is less than MinItems($Parameters{MinItems})!";
return %Result;
}
if ( $Parameters{MaxItems} && $Parameters{MaxItems} < scalar @{ $Param{EffectiveValue} } ) {
$Result{Error} = "Number of items in array is more than MaxItems($Parameters{MaxItems})!";
return %Result;
}
my @Items = ();
if (
scalar @{ $Value->[0]->{Array} }
&& $Value->[0]->{Array}->[0]->{Item}
&& ref $Value->[0]->{Array}->[0]->{Item} eq 'ARRAY'
)
{
@Items = @{ $Value->[0]->{Array}->[0]->{Item} };
}
my $DefaultItem;
INDEX:
for my $Index ( 0 .. scalar @{ $Param{EffectiveValue} } - 1 ) {
$DefaultItem = $Value->[0]->{Array}->[0]->{DefaultItem};
my $Ref = ref $Param{EffectiveValue}->[$Index];
if ($Ref) {
if ($DefaultItem) {
my $KeyNeeded;
if ( $Ref eq 'HASH' ) {
$KeyNeeded = 'Hash';
}
elsif ( $Ref eq 'ARRAY' ) {
$KeyNeeded = 'Array';
}
else {
$Result{Error} = "Wrong format!";
last INDEX;
}
if ( $DefaultItem->[0]->{Item} ) {
# So far everything is OK, we need to check deeper (recursive).
my %SubResult = $Self->_SettingEffectiveValueCheck(
XMLContentParsed => {
Value => [
{
Item => $DefaultItem,
},
],
},
EffectiveValue => $Param{EffectiveValue}->[$Index],
NoValidation => $Param{NoValidation},
CurrentSystemTime => $Param{CurrentSystemTime},
ExpireTime => $Param{ExpireTime},
UserID => $Param{UserID},
);
$Param{EffectiveValue}->[$Index] = $SubResult{EffectiveValue} if $SubResult{Success};
if ( $SubResult{Error} ) {
%Result = %SubResult;
last INDEX;
}
}
elsif ( !defined $DefaultItem->[0]->{$KeyNeeded} ) {
my $ExpectedText = '';
if ( $DefaultItem->[0]->{Array} ) {
$ExpectedText = "an array reference!";
}
elsif ( $DefaultItem->[0]->{Hash} ) {
$ExpectedText = "a hash reference!";
}
elsif ( $DefaultItem->[0]->{Content} ) {
$ExpectedText = "a scalar!";
}
$Result{Error} = "Item with index $Index must be $ExpectedText";
last INDEX;
}
else {
# So far everything is OK, we need to check deeper (recursive).
my %SubResult = $Self->_SettingEffectiveValueCheck(
XMLContentParsed => {
Value => $DefaultItem,
},
EffectiveValue => $Param{EffectiveValue}->[$Index],
NoValidation => $Param{NoValidation},
CurrentSystemTime => $Param{CurrentSystemTime},
ExpireTime => $Param{ExpireTime},
UserID => $Param{UserID},
);
$Param{EffectiveValue}->[$Index] = $SubResult{EffectiveValue} if $SubResult{Success};
if ( $SubResult{Error} ) {
%Result = %SubResult;
last INDEX;
}
}
}
else {
# Array is empty in the Defaults, value should be scalar.
$Result{Error} = "Item with index $Index must be a scalar!";
last INDEX;
}
}
else {
# scalar
if ($DefaultItem) {
if ( $DefaultItem->[0]->{Item} || $DefaultItem->[0]->{ValueType} ) {
# Item with ValueType
# So far everything is OK, we need to check deeper (recursive).
my %SubResult = $Self->_SettingEffectiveValueCheck(
XMLContentParsed => {
Value => [
{
Item => $DefaultItem,
},
],
},
EffectiveValue => $Param{EffectiveValue}->[$Index],
NoValidation => $Param{NoValidation},
CurrentSystemTime => $Param{CurrentSystemTime},
ExpireTime => $Param{ExpireTime},
UserID => $Param{UserID},
);
$Param{EffectiveValue}->[$Index] = $SubResult{EffectiveValue} if $SubResult{Success};
if ( $SubResult{Error} ) {
%Result = %SubResult;
last INDEX;
}
}
elsif ( $DefaultItem->[0]->{Hash} ) {
$Result{Error} = "Item with index $Index must be a hash reference!";
last INDEX;
}
elsif ( $DefaultItem->[0]->{Array} ) {
$Result{Error} = "Item with index $Index must be an array reference!";
last INDEX;
}
}
}
}
if ( $Result{Error} ) {
return %Result;
}
$Result{Success} = 1;
}
if ( $Result{Success} ) {
$Result{EffectiveValue} = $Param{EffectiveValue};
}
$Result{ExpireTime} = $ExpireTime;
if ($StoreCache) {
$Cache->{$SettingKey} = \%Result;
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => $Cache,
TTL => 20 * 24 * 60 * 60,
);
}
return %Result;
}
=head2 SettingReset()
Reset the modified value to the default value.
my $Result = $SysConfigObject->SettingReset(
Name => 'Setting Name', # (required) Setting name
TargetUserID => 2, # (optional) UserID for settings in AgentPreferences
# or
ExclusiveLockGUID => $LockingString, # (optional) the GUID used to locking the setting
UserID => 1, # (required) UserID that creates modification
);
Returns:
$Result = 1; # or false in case of an error
=cut
sub SettingReset {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Name ExclusiveLockGUID UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
# Check if the setting exists.
my %DefaultSetting = $SysConfigDBObject->DefaultSettingGet(
Name => $Param{Name},
);
return if !%DefaultSetting;
# Default should be locked.
my $LockedByUser = $SysConfigDBObject->DefaultSettingIsLockedByUser(
DefaultID => $DefaultSetting{DefaultID},
ExclusiveLockUserID => $Param{UserID},
ExclusiveLockGUID => $Param{ExclusiveLockGUID},
);
if ( !$LockedByUser ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Setting $Param{Name} is not locked to this user!",
);
return;
}
my %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
Name => $Param{Name},
IsGlobal => 1,
);
# Setting already had default value.
return 1 if !%ModifiedSetting;
my %SettingDeployed = $Self->SettingGet(
Name => $Param{Name},
Deployed => 1,
);
my $IsModified = DataIsDifferent(
Data1 => \$SettingDeployed{EffectiveValue},
Data2 => \$DefaultSetting{EffectiveValue},
) || 0;
$IsModified ||= $SettingDeployed{IsValid} != $DefaultSetting{IsValid};
$IsModified ||= $SettingDeployed{UserModificationActive} != $DefaultSetting{UserModificationActive};
$ModifiedSetting{IsDirty} = $IsModified ? 1 : 0;
# Copy values from default.
for my $Field (qw(IsValid UserModificationActive EffectiveValue)) {
$ModifiedSetting{$Field} = $DefaultSetting{$Field};
}
# Set reset flag.
$ModifiedSetting{ResetToDefault} = 1;
# Delete modified setting.
my $ResetResult = $SysConfigDBObject->ModifiedSettingUpdate(
%ModifiedSetting,
ExclusiveLockGUID => $Param{ExclusiveLockGUID},
UserID => $Param{UserID},
);
if ( !$ResetResult ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "System was unable to update Modified setting: $Param{Name}!",
);
}
return $ResetResult;
}
=head2 ConfigurationTranslatedGet()
Returns a hash with all settings and translated metadata.
my %Result = $SysConfigObject->ConfigurationTranslatedGet();
Returns:
%Result = (
'ACL::CacheTTL' => {
'Category' => 'OTRS',
'IsInvisible' => '0',
'Metadata' => "ACL::CacheTTL--- '3600'
Cache-Zeit in Sekunden f\x{fc}r Datenbank ACL-Backends.",
...
);
=cut
sub ConfigurationTranslatedGet {
my ( $Self, %Param ) = @_;
my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $CacheType = 'SysConfig';
my $CacheKey = "ConfigurationTranslatedGet::$LanguageObject->{UserLanguage}";
# Return cache.
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
return %{$Cache} if ref $Cache eq 'HASH';
my @SettingList = $Self->ConfigurationList(
IncludeInvisible => 1,
);
my %Result;
for my $Setting (@SettingList) {
my %SettingTranslated = $Self->_SettingTranslatedGet(
Language => $LanguageObject->{UserLanguage},
Name => $Setting->{Name},
);
# Append to the result.
$Result{ $Setting->{Name} } = $SettingTranslated{ $Setting->{Name} };
}
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \%Result,
TTL => $Self->{CacheTTL} || 24 * 60 * 60,
);
return %Result;
}
=head2 SettingNavigationToPath()
Returns path structure for given navigation group.
my @Path = $SysConfigObject->SettingNavigationToPath(
Navigation => 'Frontend::Agent::ToolBarModule', # (optional)
);
Returns:
@Path = (
{
'Value' => 'Frontend',
'Name' => 'Frontend',
},
{
'Value' => 'Frontend::Agent',
'Name' => 'Agent',
},
...
);
=cut
sub SettingNavigationToPath {
my ( $Self, %Param ) = @_;
my @NavigationNames = split( '::', $Param{Navigation} );
my @Path;
INDEX:
for my $Index ( 0 .. $#NavigationNames ) {
$Path[$Index]->{Name} = $NavigationNames[$Index];
my @SubArray = @NavigationNames[ 0 .. $Index ];
$Path[$Index]->{Value} = join '::', @SubArray;
}
return @Path;
}
=head2 ConfigurationTranslatableStrings()
Returns a unique list of all translatable strings from the default settings.
my @TranslatableStrings = $SysConfigObject->ConfigurationTranslatableStrings();
=cut
sub ConfigurationTranslatableStrings {
my ( $Self, %Param ) = @_;
# Reset translation list.
$Self->{ConfigurationTranslatableStrings} = {};
# Get all default settings.
my @SettingsList = $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingListGet();
SETTING:
for my $Setting (@SettingsList) {
next SETTING if !$Setting;
next SETTING if !defined $Setting->{XMLContentParsed};
# Get translatable strings.
$Self->_ConfigurationTranslatableStrings( Data => $Setting->{XMLContentParsed} );
}
my @Strings;
for my $Key ( sort keys %{ $Self->{ConfigurationTranslatableStrings} } ) {
push @Strings, $Key;
}
return @Strings;
}
=head2 ConfigurationEntitiesGet()
Get all entities that are referred in any enabled Setting in complete SysConfig.
my %Result = $SysConfigObject->ConfigurationEntitiesGet();
Returns:
%Result = (
'Priority' => {
'3 normal' => [
'Ticket::Frontend::AgentTicketNote###PriorityDefault',
'Ticket::Frontend::AgentTicketPhone###Priority',
...
],
},
'Queue' => {
'Postmaster' => [
'Ticket::Frontend::CustomerTicketMessage###QueueDefault',
],
'Raw' => [
'PostmasterDefaultQueue',
],
},
...
);
=cut
sub ConfigurationEntitiesGet {
my ( $Self, %Param ) = @_;
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $CacheType = "SysConfigEntities";
my $CacheKey = "UsedEntities";
my $CacheData = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
# Return cached data if available.
return %{$CacheData} if $CacheData;
my %Result = ();
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my @EntitySettings = $SysConfigDBObject->DefaultSettingSearch(
Search => 'ValueEntityType',
);
SETTING:
for my $SettingName (@EntitySettings) {
my %Setting = $SysConfigDBObject->DefaultSettingGet(
Name => $SettingName,
);
# Check if there is modified value.
my %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
Name => $SettingName,
);
if (%ModifiedSetting) {
my $XMLContentParsed = $Self->SettingModifiedXMLContentParsedGet(
ModifiedSetting => \%ModifiedSetting,
DefaultSetting => \%Setting,
);
$Setting{XMLContentParsed}->{Value} = $XMLContentParsed;
}
%Result = $Self->_ConfigurationEntitiesGet(
Value => $Setting{XMLContentParsed}->{Value},
Result => \%Result,
Name => $Setting{XMLContentParsed}->{Name},
);
}
# Cache the results.
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \%Result,
TTL => 30 * 24 * 60 * 60,
);
return %Result;
}
=head2 ConfigurationEntityCheck()
Check if there are any enabled settings that refers to the provided Entity.
my @Result = $SysConfigObject->ConfigurationEntityCheck(
EntityType => 'Priority',
EntityName => '3 normal',
);
Returns:
@Result = (
'Ticket::Frontend::AgentTicketNote###PriorityDefault',
'Ticket::Frontend::AgentTicketPhone###Priority',
'Ticket::Frontend::AgentTicketBulk###PriorityDefault',
...
);
=cut
sub ConfigurationEntityCheck {
my ( $Self, %Param ) = @_;
for my $Needed (qw(EntityType EntityName)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!"
);
return;
}
}
if ( !$Param{EntityType} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "EntityType is invalid!"
);
return;
}
# If name is an empty string there is nothing to do, return an empty array.
return () if !$Param{EntityName};
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $CacheType = "SysConfigEntities";
my $CacheKey = "ConfigurationEntityCheck::$Param{EntityType}::$Param{EntityName}";
my $CacheData = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
# Return cached data if available.
return @{$CacheData} if $CacheData;
my %EntitySettings = $Self->ConfigurationEntitiesGet();
my @Result = ();
for my $EntityType ( sort keys %EntitySettings ) {
# Check conditions.
if (
$EntityType eq $Param{EntityType}
&& $EntitySettings{$EntityType}{ $Param{EntityName} }
)
{
@Result = @{ $EntitySettings{$EntityType}->{ $Param{EntityName} } };
}
}
# Cache the results.
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \@Result,
TTL => 30 * 24 * 60 * 60,
);
return @Result;
}
=head2 ConfigurationXML2DB()
Load Settings defined in XML files to the database.
my $Success = $SysConfigObject->ConfigurationXML2DB(
UserID => 1, # UserID
Directory => '/some/folder', # (optional) Provide directory where XML files are stored (default: Kernel/Config/Files/XML).
Force => 1, # (optional) Force Setting update, even if it's locked by another user. Default: 0.
CleanUp => 1, # (optional) Remove all settings that are not present in XML files. Default: 0.
);
Returns:
$Success = 1; # or false in case of an error.
=cut
sub ConfigurationXML2DB {
my ( $Self, %Param ) = @_;
if ( !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need UserID!"
);
return;
}
my $Directory = $Param{Directory} || "$Self->{Home}/Kernel/Config/Files/XML/";
if ( !-e $Directory ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Directory '$Directory' does not exists",
);
return;
}
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
# Load xml config files, ordered by file name
my @Files = $MainObject->DirectoryRead(
Directory => $Directory,
Filter => "*.xml",
);
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $SysConfigXMLObject = $Kernel::OM->Get('Kernel::System::SysConfig::XML');
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my %SettingsByInit = (
Framework => [],
Application => [],
Config => [],
Changes => [],
);
my %Data;
FILE:
for my $File (@Files) {
my $MD5Sum = $MainObject->MD5sum(
Filename => $File,
);
# Cleanup filename for cache type
my $Filename = $File;
$Filename =~ s{\/\/}{\/}g;
$Filename =~ s{\A .+ Kernel/Config/Files/XML/ (.+)\.xml\z}{$1}msx;
$Filename =~ s{\A .+ scripts/test/sample/SysConfig/XML/ (.+)\.xml\z}{$1}msx;
my $CacheType = 'SysConfigPersistent';
my $CacheKey = "ConfigurationXML2DB::${Filename}::${MD5Sum}";
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
if (
ref $Cache eq 'HASH'
&& $Cache->{Init}
&& ref $Cache->{Settings} eq 'ARRAY'
)
{
@{ $SettingsByInit{ $Cache->{Init} } }
= ( @{ $SettingsByInit{ $Cache->{Init} } }, @{ $Cache->{Settings} } );
next FILE;
}
# Read XML file.
my $ConfigFile = $MainObject->FileRead(
Location => $File,
Mode => 'utf8',
Result => 'SCALAR',
);
if ( !ref $ConfigFile || !${$ConfigFile} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Can't open file $File: $!",
);
next FILE;
}
# Check otrs_config Init attribute.
$$ConfigFile =~ m{<otrs_config.*?init="(.*?)"}gsmx;
my $InitValue = $1;
# Check if InitValue is Valid.
if ( !defined $SettingsByInit{$InitValue} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"Invalid otrs_config Init value ($InitValue)! Allowed values: Framework, Application, Config, Changes.",
);
next FILE;
}
my $XMLFilename = $File;
$XMLFilename =~ s{$Directory(.*\.xml)\z}{$1}gmsx;
$XMLFilename =~ s{\A/}{}gmsx;
# Remove comments.
${$ConfigFile} =~ s{<!--.*?-->}{}gs;
my @ParsedSettings = $SysConfigXMLObject->SettingListParse(
XMLInput => ${$ConfigFile},
XMLFilename => $XMLFilename,
);
@{ $SettingsByInit{$InitValue} } = ( @{ $SettingsByInit{$InitValue} }, @ParsedSettings );
# There might be an error parsing file. If we cache the result, error message will not be present.
if (@ParsedSettings) {
$CacheObject->Set(
Key => $CacheKey,
Type => $CacheType,
Value => {
Init => $InitValue,
Settings => \@ParsedSettings,
},
TTL => 60 * 60 * 24 * 20,
);
}
}
# Combine everything together in the correct order.
my %Settings;
for my $Init (qw(Framework Application Config Changes)) {
SETTING:
for my $Setting ( @{ $SettingsByInit{$Init} } ) {
my $Name = $Setting->{XMLContentParsed}->{Name};
next SETTING if !$Name;
$Settings{$Name} = $Setting;
}
}
# Find and remove all settings that are in DB, but are not defined in XML files.
if ( $Param{CleanUp} ) {
$Self->_DBCleanUp( Settings => \%Settings );
}
# Lock all settings to be able to update them if needed.
my $ExclusiveLockGUID = $SysConfigDBObject->DefaultSettingLock(
UserID => $Param{UserID},
LockAll => 1,
);
if ( !$ExclusiveLockGUID ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "System was unable to lock Default Settings ",
);
return;
}
my @SettingList = $Self->ConfigurationList(
IncludeInvisible => 1,
);
my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
if ( !@SettingList ) {
my $Success = $Self->_DefaultSettingAddBulk(
Settings => \%Settings,
SettingList => \@SettingList,
UserID => $Param{UserID},
);
return if !$Success;
}
else {
my %DefaultSettingsAdd;
# Build setting list hash, to avoid slow grep expressions.
my %SettingListLookup = map { $_->{Name} => $_ } @SettingList;
# Create/Update settings in DB.
SETTING:
for my $SettingName ( sort keys %Settings ) {
my $DefaultSetting = $SettingListLookup{$SettingName};
if ( IsHashRefWithData($DefaultSetting) ) {
# Compare new Setting XML with the old one (skip if there is no difference).
my $Updated = $Settings{$SettingName}->{XMLContentRaw} ne $DefaultSetting->{XMLContentRaw};
$Updated ||= $Settings{$SettingName}->{XMLFilename} ne $DefaultSetting->{XMLFilename};
next SETTING if !$Updated;
# Create a local clone of the value to prevent any modification.
my $Value = $StorableObject->Clone(
Data => $Settings{$SettingName}->{XMLContentParsed}->{Value},
);
my $EffectiveValue = $Self->SettingEffectiveValueGet(
Value => $Value,
);
# Update default setting.
my $Success = $SysConfigDBObject->DefaultSettingUpdate(
DefaultID => $DefaultSetting->{DefaultID},
Name => $Settings{$SettingName}->{XMLContentParsed}->{Name},
Description => $Settings{$SettingName}->{XMLContentParsed}->{Description}->[0]->{Content} || '',
Navigation => $Settings{$SettingName}->{XMLContentParsed}->{Navigation}->[0]->{Content} || '',
IsInvisible => $Settings{$SettingName}->{XMLContentParsed}->{Invisible} || 0,
IsReadonly => $Settings{$SettingName}->{XMLContentParsed}->{ReadOnly} || 0,
IsRequired => $Settings{$SettingName}->{XMLContentParsed}->{Required} || 0,
IsValid => $Settings{$SettingName}->{XMLContentParsed}->{Valid} || 0,
HasConfigLevel => $Settings{$SettingName}->{XMLContentParsed}->{ConfigLevel} || 100,
UserModificationPossible => $Settings{$SettingName}->{XMLContentParsed}->{UserModificationPossible}
|| 0,
UserModificationActive => $Settings{$SettingName}->{XMLContentParsed}->{UserModificationActive}
|| 0,
UserPreferencesGroup => $Settings{$SettingName}->{XMLContentParsed}->{UserPreferencesGroup},
XMLContentRaw => $Settings{$SettingName}->{XMLContentRaw},
XMLContentParsed => $Settings{$SettingName}->{XMLContentParsed},
XMLFilename => $Settings{$SettingName}->{XMLFilename},
EffectiveValue => $EffectiveValue,
UserID => $Param{UserID},
ExclusiveLockGUID => $ExclusiveLockGUID,
);
if ( !$Success ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"DefaultSettingUpdate failed for Config Item: $SettingName!",
);
}
my @ModifiedList = $SysConfigDBObject->ModifiedSettingListGet(
Name => $Settings{$SettingName}->{XMLContentParsed}->{Name},
);
for my $ModifiedSetting (@ModifiedList) {
# So far everything is OK, if the structure or values does
# not match anymore, modified values must be deleted.
my %ValueCheckResult = $Self->SettingEffectiveValueCheck(
EffectiveValue => $ModifiedSetting->{EffectiveValue},
XMLContentParsed => $Settings{$SettingName}->{XMLContentParsed},
SettingUID => $ModifiedSetting->{SettingUID},
StoreCache => 1,
UserID => $Param{UserID},
);
if ( !$ValueCheckResult{Success} ) {
$SysConfigDBObject->ModifiedSettingDelete(
ModifiedID => $ModifiedSetting->{ModifiedID},
);
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => $ValueCheckResult{Error},
);
}
}
}
else {
# Create a local clone of the value to prevent any modification.
my $Value = $StorableObject->Clone(
Data => $Settings{$SettingName}->{XMLContentParsed}->{Value},
);
my $EffectiveValue = $Self->SettingEffectiveValueGet(
Value => $Value,
);
$DefaultSettingsAdd{ $Settings{$SettingName}->{XMLContentParsed}->{Name} } = {
Name => $Settings{$SettingName}->{XMLContentParsed}->{Name},
Description => $Settings{$SettingName}->{XMLContentParsed}->{Description}->[0]->{Content} || '',
Navigation => $Settings{$SettingName}->{XMLContentParsed}->{Navigation}->[0]->{Content} || '',
IsInvisible => $Settings{$SettingName}->{XMLContentParsed}->{Invisible} || 0,
IsReadonly => $Settings{$SettingName}->{XMLContentParsed}->{ReadOnly} || 0,
IsRequired => $Settings{$SettingName}->{XMLContentParsed}->{Required} || 0,
IsValid => $Settings{$SettingName}->{XMLContentParsed}->{Valid} || 0,
HasConfigLevel => $Settings{$SettingName}->{XMLContentParsed}->{ConfigLevel} || 100,
UserModificationPossible => $Settings{$SettingName}->{XMLContentParsed}->{UserModificationPossible}
|| 0,
UserModificationActive => $Settings{$SettingName}->{XMLContentParsed}->{UserModificationActive}
|| 0,
UserPreferencesGroup => $Settings{$SettingName}->{XMLContentParsed}->{UserPreferencesGroup},
XMLContentRaw => $Settings{$SettingName}->{XMLContentRaw},
XMLContentParsed => $Settings{$SettingName}->{XMLContentParsed},
XMLFilename => $Settings{$SettingName}->{XMLFilename},
EffectiveValue => $EffectiveValue,
NoCleanup => 1,
UserID => $Param{UserID},
};
# Delete individual cache.
$CacheObject->Delete(
Type => 'SysConfigDefault',
Key => 'DefaultSettingGet::' . $Settings{$SettingName}->{XMLContentParsed}->{Name},
);
}
}
if (%DefaultSettingsAdd) {
my $Success = $Self->_DefaultSettingAddBulk(
Settings => \%DefaultSettingsAdd,
SettingList => \@SettingList,
UserID => $Param{UserID},
);
return if !$Success;
}
}
# Unlock all the settings so they can be locked again afterwards.
$SysConfigDBObject->DefaultSettingUnlock(
UnlockAll => 1,
);
return 1;
}
=head2 ConfigurationNavigationTree()
Returns navigation tree in the hash format.
my %Result = $SysConfigObject->ConfigurationNavigationTree(
RootNavigation => 'Parent', # (optional) If provided only sub groups of the root navigation are returned.
UserModificationActive => 1, # (optional) Return settings that can be modified on user level only.
IsValid => 1, # (optional) By default, display all settings.
Category => 'OTRS' # (optional)
);
Returns:
%Result = (
'Core' => {
'Core::Cache' => {},
'Core::CustomerCompany' => {},
'Core::CustomerUser' => {},
'Core::Daemon' => {
'Core::Daemon::ModuleRegistration' => {},
},
...
'Crypt' =>{
...
},
...
);
=cut
sub ConfigurationNavigationTree {
my ( $Self, %Param ) = @_;
$Param{RootNavigation} //= '';
$Param{UserModificationActive} //= '0';
my $CacheType = 'SysConfigNavigation';
my $CacheKey = "NavigationTree::$Param{RootNavigation}::$Param{UserModificationActive}";
if ( defined $Param{IsValid} ) {
if ( $Param{IsValid} ) {
$CacheKey .= '::Valid';
}
else {
$CacheKey .= '::Invalid';
}
}
if ( defined $Param{Category} && $Param{Category} ) {
if ( $Param{Category} eq 'All' ) {
delete $Param{Category};
}
else {
$CacheKey .= "::Category=$Param{Category}";
}
}
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
return %{$Cache} if ref $Cache eq 'HASH';
my %CategoryOptions;
if ( $Param{Category} ) {
my %Categories = $Self->ConfigurationCategoriesGet();
if ( $Categories{ $Param{Category} } ) {
%CategoryOptions = (
Category => $Param{Category},
CategoryFiles => $Categories{ $Param{Category} }->{Files},
);
}
}
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
# Get all default settings
my @SettingsRaw = $SysConfigDBObject->DefaultSettingListGet(
%CategoryOptions,
UserModificationActive => $Param{UserModificationActive} || undef,
IsValid => $Param{IsValid},
);
# For AgentPreference take into account which settings are Forbidden to update by user or disabled when counting
# settings. See bug#13488 (https://bugs.otrs.org/show_bug.cgi?id=13488).
if ( $Param{Action} && $Param{Action} eq 'AgentPreferences' ) {
# Get List of all modified settings which are valid and forbidden to update by user.
my @ForbiddenSettings = $SysConfigDBObject->ModifiedSettingListGet(
%CategoryOptions,
UserModificationActive => 0,
IsValid => 1,
);
# Get List of all modified settings which are invalid and allowed to update by user.
my @InvalidSettings = $SysConfigDBObject->ModifiedSettingListGet(
%CategoryOptions,
UserModificationActive => 1,
IsValid => 0,
);
my @ModifiedSettings;
for my $Setting (@SettingsRaw) {
push @ModifiedSettings, $Setting
if !grep { $_->{Name} eq $Setting->{Name} } ( @ForbiddenSettings, @InvalidSettings );
}
@SettingsRaw = @ModifiedSettings;
# Add settings which by default are not UserModifiedActive and are changed, to the navigation list
# in preference screen. Please see bug#13489 for more information.
@ModifiedSettings = $SysConfigDBObject->ModifiedSettingListGet(
%CategoryOptions,
UserModificationActive => 1,
IsValid => 1,
);
for my $Setting (@ModifiedSettings) {
my %DefaultSetting = $SysConfigDBObject->DefaultSettingGet(
Name => $Setting->{Name},
);
if ( !grep { $_->{Name} eq $DefaultSetting{Name} } @SettingsRaw ) {
push @SettingsRaw, \%DefaultSetting;
}
}
}
my @Settings;
# Skip invisible settings from the navigation tree
SETTING:
for my $Setting (@SettingsRaw) {
next SETTING if $Setting->{IsInvisible};
push @Settings, {
Name => $Setting->{Name},
Navigation => $Setting->{Navigation},
};
}
my %Result = ();
my @RootNavigation;
if ( $Param{RootNavigation} ) {
@RootNavigation = split "::", $Param{RootNavigation};
}
# Remember ancestors.
for my $Index ( 1 .. $#RootNavigation ) {
$RootNavigation[$Index] = $RootNavigation[ $Index - 1 ] . '::' . $RootNavigation[$Index];
}
SETTING:
for my $Setting (@Settings) {
next SETTING if !$Setting->{Navigation};
my @Path = split "::", $Setting->{Navigation};
# Remember ancestors.
for my $Index ( 1 .. $#Path ) {
$Path[$Index] = $Path[ $Index - 1 ] . '::' . $Path[$Index];
}
# Check if RootNavigation matches current setting.
for my $Index ( 0 .. $#RootNavigation ) {
next SETTING if !$Path[$Index];
if ( $RootNavigation[$Index] ne $Path[$Index] ) {
next SETTING;
}
}
# Remove root groups from Path.
for my $Index ( 0 .. $#RootNavigation ) {
shift @Path;
}
%Result = $Self->_NavigationTree(
Tree => \%Result,
Array => \@Path,
);
}
# Until now we have strucure of the Navigation tree without sub-node count. We need this number to disable
# click on empty nodes. We could implement that in the _NavigationTree, but it's not efficient(loop of 1800+ settings).
# Instead, we extend result in the _NavigationTreeNodeCount.
%Result = $Self->_NavigationTreeNodeCount(
Tree => \%Result,
Settings => \@Settings,
);
# Cache the results.
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \%Result,
TTL => 30 * 24 * 60 * 60,
);
return %Result;
}
=head2 ConfigurationListGet()
Returns list of settings that matches provided parameters.
my @List = $SysConfigObject->ConfigurationListGet(
Navigation => 'SomeNavigationGroup', # (optional) limit to the settings that have provided navigation
TargetUserID => 2, # (optional) if provided, system returns setting for particular user only,
# otherwise, returns global setting list
IsValid => 1, # (optional) by default returns valid and invalid settings.
Invisible => 0, # (optional) Include Invisible settings. By default, not included.
UserPreferencesGroup => 'Advanced', # (optional) filter list by group.
Translate => 0, # (optional) Translate translatable string in EffectiveValue. Default 0.
OverriddenInXML => 1, # (optional) Consider changes made in Perl files. Default 0. Use it in modules only!
UserID => 1, # Required if OverriddenInXML is set.
);
Returns:
@List = (
{
DefaultID => 123,
ModifiedID => 456, # if modified
Name => "ProductName",
Description => "Defines the name of the application ...",
Navigation => "ASimple::Path::Structure",
IsInvisible => 1,
IsReadonly => 0,
IsRequired => 1,
IsValid => 1,
HasConfigLevel => 200,
UserModificationPossible => 0, # 1 or 0
UserModificationActive => 0, # 1 or 0
UserPreferencesGroup => 'Advanced', # optional
XMLContentRaw => "The XML structure as it is on the config file",
XMLContentParsed => "XML parsed to Perl",
XMLFilename => "Daemon.xml",
EffectiveValue => "Product 6",
DefaultValue => "Product 5",
IsModified => 1, # 1 or 0
IsDirty => 1, # 1 or 0
ExclusiveLockGUID => 'A32CHARACTERLONGSTRINGFORLOCKING',
ExclusiveLockUserID => 1,
ExclusiveLockExpiryTime => '2016-05-29 11:09:04',
CreateTime => "2016-05-29 11:04:04",
ChangeTime => "2016-05-29 11:04:04",
OverriddenFileName => 'ZZZDefauls.pm'
},
{
DefaultID => 321,
Name => 'FieldName',
# ...
CreateTime => '2010-09-11 10:08:00',
ChangeTime => '2011-01-01 01:01:01',
},
# ...
);
=cut
sub ConfigurationListGet {
my ( $Self, %Param ) = @_;
if ( $Param{OverriddenInXML} && !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'UserID is needed when OverriddenInXML is set!',
);
return;
}
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
$Param{Translate} //= 0; # don't translate by default
my %CategoryOptions;
if ( $Param{Category} ) {
my %Categories = $Self->ConfigurationCategoriesGet();
if ( $Categories{ $Param{Category} } ) {
%CategoryOptions = (
Category => $Param{Category},
CategoryFiles => $Categories{ $Param{Category} }->{Files},
);
}
}
# Get all default settings for this navigation group.
my @ConfigurationList = $SysConfigDBObject->DefaultSettingListGet(
Navigation => $Param{Navigation},
UserModificationPossible => $Param{TargetUserID} ? 1 : undef,
UserPreferencesGroup => $Param{UserPreferencesGroup} || undef,
IsInvisible => $Param{Invisible} ? undef : 0,
%CategoryOptions,
);
my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
# Update setting values with the modified settings.
SETTING:
for my $Setting (@ConfigurationList) {
if ( $Param{TargetUserID} ) {
my %SettingGlobal = $Self->SettingGet(
Name => $Setting->{Name},
IsGlobal => 1,
Translate => $Param{Translate},
);
if ( %SettingGlobal && $SettingGlobal{ModifiedID} ) {
# There is modified setting, but we need last deployed version.
my %SettingDeployed = $SysConfigDBObject->ModifiedSettingVersionGetLast(
Name => $Setting->{Name},
);
if ( !IsHashRefWithData( \%SettingDeployed ) ) {
%SettingDeployed = $Self->SettingGet(
Name => $Setting->{Name},
Default => 1,
Translate => $Param{Translate},
);
}
$Setting = {
%SettingGlobal,
%SettingDeployed,
};
}
else {
# Use default value.
my %SettingDefault = $Self->SettingGet(
Name => $Setting->{Name},
Default => 1,
Translate => $Param{Translate},
);
$Setting = \%SettingDefault;
}
}
# Remember default value.
$Setting->{DefaultValue} = $Setting->{EffectiveValue};
if ( ref $Setting->{EffectiveValue} ) {
$Setting->{DefaultValue} = $StorableObject->Clone(
Data => $Setting->{EffectiveValue},
);
}
my %ModifiedSetting = $Self->SettingGet(
Name => $Setting->{Name},
TargetUserID => $Param{TargetUserID} // undef,
Translate => $Param{Translate},
OverriddenInXML => $Param{OverriddenInXML},
UserID => $Param{UserID},
);
# Skip if setting is invalid.
next SETTING if !IsHashRefWithData( \%ModifiedSetting );
next SETTING if !defined $ModifiedSetting{EffectiveValue};
# Mark setting as modified.
my $IsModified = DataIsDifferent(
Data1 => \$Setting->{EffectiveValue},
Data2 => \$ModifiedSetting{EffectiveValue},
) || 0;
$IsModified ||= $ModifiedSetting{IsValid} != $Setting->{IsValid};
$IsModified ||= $ModifiedSetting{UserModificationActive} != $Setting->{UserModificationActive};
$Setting->{IsModified} = $IsModified ? 1 : 0;
# Update setting attributes.
ATTRIBUTE:
for my $Attribute (
qw(ModifiedID IsValid UserModificationActive UserPreferencesGroup EffectiveValue IsDirty ChangeTime XMLContentParsed SettingUID OverriddenFileName)
)
{
next ATTRIBUTE if !defined $ModifiedSetting{$Attribute};
$Setting->{$Attribute} = $ModifiedSetting{$Attribute};
}
}
if ( defined $Param{IsValid} ) {
@ConfigurationList = grep { $_->{IsValid} == $Param{IsValid} } @ConfigurationList;
}
if ( $Param{TargetUserID} ) {
# List contains all settings that can be activated. Get only those that are really activated.
@ConfigurationList = grep { $_->{UserModificationActive} } @ConfigurationList;
}
return @ConfigurationList;
}
=head2 ConfigurationList()
Wrapper of Kernel::System::SysConfig::DB::DefaultSettingList() - Get list of all settings.
my @SettingList = $SysConfigObject->ConfigurationList();
Returns:
@SettingList = (
{
DefaultID => '123',
Name => 'SettingName1',
IsDirty => 1,
},
{
DefaultID => '124',
Name => 'SettingName2',
IsDirty => 0
},
...
);
=cut
sub ConfigurationList {
my ( $Self, %Param ) = @_;
return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingList(%Param);
}
=head2 ConfigurationInvalidList()
Returns list of enabled settings that have invalid effective value.
my @List = $SysConfigObject->ConfigurationInvalidList(
CachedOnly => 0, # (optional) Default 0. If enabled, system will return cached value.
# If there is no cache yet, system will return empty list, but
# it will also trigger async call to generate cache.
Undeployed => 1, # (optional) Default 0. Check settings that are not deployed as well.
NoCache => 1, # (optional) Default 0. If enabled, system won't check the cached valuue.
);
Returns:
@List = ( "Setting1", "Setting5", ... );
=cut
sub ConfigurationInvalidList {
my ( $Self, %Param ) = @_;
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $CacheType = 'SysConfig';
my $CacheKey = 'ConfigurationInvalidList';
if ( $Param{Undeployed} ) {
$CacheKey .= '::Undeployed';
}
# Return cache.
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
return @{$Cache} if ref $Cache eq 'ARRAY' && !$Param{NoCache};
if ( $Param{CachedOnly} ) {
# There is no cache but caller expects quick answer. Return empty array, but create cache in async call.
$Self->AsyncCall(
ObjectName => 'Kernel::System::SysConfig',
FunctionName => 'ConfigurationInvalidList',
FunctionParams => {},
MaximumParallelInstances => 1,
);
return ();
}
my @SettingsEnabled = $Self->ConfigurationListGet(
IsValid => 1,
Translate => 0,
);
my @InvalidSettings;
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $CurrentSystemTime = $DateTimeObject->ToEpoch();
$DateTimeObject->Add(
Months => 1,
);
my $ExpireTime = $DateTimeObject->ToEpoch();
for my $Setting (@SettingsEnabled) {
my %SettingData = $Self->SettingGet(
Name => $Setting->{Name},
Deployed => $Param{Undeployed} ? undef : 1,
NoCache => $Param{NoCache},
);
my %EffectiveValueCheck = $Self->SettingEffectiveValueCheck(
EffectiveValue => $SettingData{EffectiveValue},
XMLContentParsed => $Setting->{XMLContentParsed},
CurrentSystemTime => $CurrentSystemTime,
ExpireTime => $ExpireTime,
UserID => 1,
);
if ( $EffectiveValueCheck{Error} ) {
push @InvalidSettings, $Setting->{Name};
}
}
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \@InvalidSettings,
TTL => 60 * 60, # 1 hour
);
return @InvalidSettings;
}
=head2 ConfigurationDeploy()
Write configuration items from database into a perl module file.
my %Result = $SysConfigObject->ConfigurationDeploy(
Comments => "Some comments", # (optional)
NoValidation => 0, # (optional) 1 or 0, default 0, skips settings validation
UserID => 123, # if ExclusiveLockGUID is used, UserID must match the user that creates the lock
Force => 1, # (optional) proceed even if lock is set to another user
NotDirty => 1, # (optional) do not use any values from modified dirty settings
AllSettings => 1, # (optional) use dirty modified settings from all users
DirtySettings => [ # (optional) use only this dirty modified settings from the current user
'SettingOne',
'SettingTwo',
],
);
Returns:
%Result = (
Success => 1, # Deployment successful.
);
or
%Result = (
Success => 0, # Deployment failed.
Error => 'Error...', # Error message (if available)
);
=cut
sub ConfigurationDeploy {
my ( $Self, %Param ) = @_;
my %Result = (
Success => 0,
);
if ( !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need UserID!",
);
return %Result;
}
if ( !IsPositiveInteger( $Param{UserID} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "UserID is invalid!",
);
return %Result;
}
my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
if ( $Param{AllSettings} ) {
$Param{NotDirty} = 0;
$Param{DirtySettings} = undef;
}
elsif ( $Param{NotDirty} ) {
$Param{AllSettings} = 0;
$Param{DirtySettings} = undef;
}
$Param{NoValidation} //= 0;
my $BasePath = 'Kernel/Config/Files/';
# Parameter 'FileName' is intentionally not documented in the API as it is only used for testing.
my $TargetPath = $BasePath . ( $Param{FileName} || "ZZZAAuto.pm" );
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my @UserDirtySettings = $SysConfigDBObject->ModifiedSettingListGet(
IsDirty => 1,
ChangeBy => $Param{UserID},
);
my %UserDirtySettingsLookup = map { $_->{Name} => 1 } @UserDirtySettings;
# Determine dirty settings to deploy (if not specified get all dirty settings from current user).
if ( !$Param{DirtySettings} && !$Param{AllSettings} && !$Param{NotDirty} ) {
@{ $Param{DirtySettings} } = keys %UserDirtySettingsLookup;
}
elsif ( $Param{DirtySettings} && !$Param{AllSettings} && !$Param{NotDirty} ) {
my @DirtySettings;
SETTING:
for my $Setting ( @{ $Param{DirtySettings} } ) {
next SETTING if !$UserDirtySettingsLookup{$Setting};
push @DirtySettings, $Setting;
}
$Param{DirtySettings} = \@DirtySettings;
}
my @DirtyDefaultList = $SysConfigDBObject->DefaultSettingList(
IsDirty => 1,
);
my $AddNewDeployment;
# Check if deployment is really needed
if ( $Param{NotDirty} ) {
# Check if default settings are to be deployed
if (@DirtyDefaultList) {
$AddNewDeployment = 1;
}
}
elsif ( $Param{AllSettings} ) {
# Check if default settings are to be deployed
if (@DirtyDefaultList) {
$AddNewDeployment = 1;
}
else {
my @DirtyModifiedSettings = $SysConfigDBObject->ModifiedSettingListGet(
IsDirty => 1,
);
# Check if modified settings are to be deployed
if (@DirtyModifiedSettings) {
$AddNewDeployment = 1;
}
}
}
elsif ( $Param{DirtySettings} ) {
# Check if default settings or user modified settings are to be deployed
if ( @DirtyDefaultList || IsArrayRefWithData( $Param{DirtySettings} ) ) {
$AddNewDeployment = 1;
}
}
# In case none of the previous options applied and there is no deployment in the database,
# a new deployment is needed.
my %LastDeployment = $SysConfigDBObject->DeploymentGetLast();
if ( !%LastDeployment ) {
$AddNewDeployment = 1;
}
my $EffectiveValueStrg = '';
my @Settings = $Self->_GetSettingsToDeploy(
%Param,
NoCache => %LastDeployment ? 0 : 1, # do not cache only during initial rebuild config
);
my %EffectiveValueCheckResult;
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $CurrentSystemTime = $DateTimeObject->ToEpoch();
$DateTimeObject->Add(
Months => 1,
);
my $ExpireTime = $DateTimeObject->ToEpoch();
SETTING:
for my $CurrentSetting (@Settings) {
next SETTING if !$CurrentSetting->{IsValid};
my %EffectiveValueCheck = $Self->SettingEffectiveValueCheck(
XMLContentParsed => $CurrentSetting->{XMLContentParsed},
EffectiveValue => $CurrentSetting->{EffectiveValue},
NoValidation => $Param{NoValidation},
SettingUID => $CurrentSetting->{SettingUID},
CurrentSystemTime => $CurrentSystemTime,
ExpireTime => $ExpireTime,
UserID => $Param{UserID},
);
# Instead of caching for each setting(1800+), skip caching, but remember results and cache only once.
my $ValueString = $Param{EffectiveValue} // '';
if ( ref $ValueString ) {
my $String = $StorableObject->Serialize(
Data => $Param{EffectiveValue},
);
$ValueString = $MainObject->MD5sum(
String => \$String,
);
}
my $SettingKey = "$CurrentSetting->{SettingUID}::${ValueString}";
$EffectiveValueCheckResult{$SettingKey} = \%EffectiveValueCheck;
next SETTING if $EffectiveValueCheck{Success};
# Check if setting is overridden, in this case allow deployemnt.
my $OverriddenFileName = $Self->OverriddenFileNameGet(
SettingName => $CurrentSetting->{Name},
UserID => $Param{UserID},
EffectiveValue => $CurrentSetting->{EffectiveValue},
);
if ($OverriddenFileName) {
# Setting in the DB has invalid value, but it's overridden in perl file.
# Note: This check can't be moved to the SettingEffectiveValueCheck(), since it works with Cache,
# so if perl file is updated, changes won't be reflected.
next SETTING;
}
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Setting $CurrentSetting->{Name} Effective value is not correct: $EffectiveValueCheck{Error}",
);
$Result{Error} = $LanguageObject->Translate( "Invalid setting: %s", $CurrentSetting->{Name} );
return %Result;
}
# Set cache for SettingEffectiveValueCheck().
$Self->_SettingEffectiveValueCheckCacheSet(
Value => \%EffectiveValueCheckResult,
NoValidation => $Param{NoValidation},
);
# Combine settings effective values into a perl string
if ( IsArrayRefWithData( \@Settings ) ) {
$EffectiveValueStrg = $Self->_EffectiveValues2PerlFile(
Settings => \@Settings,
TargetPath => $TargetPath,
);
if ( !defined $EffectiveValueStrg ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not combine settings values into a perl hash",
);
$Result{Error} = $LanguageObject->Translate("Could not combine settings values into a perl hash.");
return %Result;
}
}
# Force new deployment if current DB settings are different from the last deployment.
if ( !$AddNewDeployment ) {
# Remove CurrentDeploymentID line for easy compare.
my $LastDeploymentStrg = $LastDeployment{EffectiveValueStrg};
$LastDeploymentStrg =~ s{\$Self->\{'CurrentDeploymentID'\} [ ] = [ ] '\d+';\n}{}msx;
if ( $EffectiveValueStrg ne $LastDeploymentStrg ) {
$AddNewDeployment = 1;
}
}
if ($AddNewDeployment) {
# Lock the deployment to be able add it to the DB.
my $ExclusiveLockGUID = $SysConfigDBObject->DeploymentLock(
UserID => $Param{UserID},
Force => $Param{Force},
);
if ( !$ExclusiveLockGUID ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Can not lock the deployment for UserID '$Param{UserID}'!",
);
$Result{Error} = $LanguageObject->Translate(
"Can not lock the deployment for UserID '%s'!",
$Param{UserID},
);
return %Result;
}
# Get system time stamp (string formated).
my $DateTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime'
);
my $TimeStamp = $DateTimeObject->ToString();
my $HandleSettingsSuccess = $Self->_HandleSettingsToDeploy(
%Param,
DeploymentExclusiveLockGUID => $ExclusiveLockGUID,
DeploymentTimeStamp => $TimeStamp,
);
my $DeploymentID;
if ($HandleSettingsSuccess) {
# Add a new deployment in the DB.
$DeploymentID = $SysConfigDBObject->DeploymentAdd(
Comments => $Param{Comments},
EffectiveValueStrg => \$EffectiveValueStrg,
ExclusiveLockGUID => $ExclusiveLockGUID,
DeploymentTimeStamp => $TimeStamp,
UserID => $Param{UserID},
);
if ( !$DeploymentID ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not create the deployment in the DB!",
);
}
}
# Unlock the deployment, so new deployments can be added afterwards.
my $Unlock = $SysConfigDBObject->DeploymentUnlock(
ExclusiveLockGUID => $ExclusiveLockGUID,
UserID => $Param{UserID},
);
if ( !$Unlock ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not remove deployment lock for UserID '$Param{UserID}'",
);
}
# Make sure to return on errors after we unlock the deployment.
if ( !$HandleSettingsSuccess || !$DeploymentID ) {
return %Result;
}
# If setting is updated on global level, check all user specific settings, maybe it's needed
# to remove duplicates.
if ( $Self->can('UserConfigurationResetToGlobal') ) { # OTRS Business Solution™
my @DeployedSettings;
if ( $Param{DirtySettings} ) {
@DeployedSettings = @{ $Param{DirtySettings} };
}
else {
for my $Setting (@Settings) {
push @DeployedSettings, $Setting->{Name};
}
}
if ( scalar @DeployedSettings ) {
$Self->UserConfigurationResetToGlobal(
Settings => \@DeployedSettings,
UserID => $Param{UserID},
);
}
}
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# Delete categories cache.
$CacheObject->Delete(
Type => 'SysConfig',
Key => 'ConfigurationCategoriesGet',
);
$CacheObject->Delete(
Type => 'SysConfig',
Key => 'ConfigurationInvalidList',
);
$CacheObject->Delete(
Type => 'SysConfig',
Key => 'ConfigurationInvalidList::Undeployed',
);
}
else {
$EffectiveValueStrg = $LastDeployment{EffectiveValueStrg};
}
# Base folder for deployment could be not present.
if ( !-d $BasePath ) {
mkdir $BasePath;
}
$Result{Success} = $Self->_FileWriteAtomic(
Filename => "$Self->{Home}/$TargetPath",
Content => \$EffectiveValueStrg,
);
return %Result;
}
=head2 ConfigurationDeployList()
Get deployment list with complete data.
my @List = $SysConfigObject->ConfigurationDeployList();
Returns:
@List = (
{
DeploymentID => 123,
Comments => 'Some Comments',
EffectiveValueStrg => $SettingEffectiveValues, # String with the value of all settings,
# as seen in the Perl configuration file.
CreateTime => "2016-05-29 11:04:04",
CreateBy => 123,
},
{
DeploymentID => 456,
Comments => 'Some Comments',
EffectiveValueStrg => $SettingEffectiveValues2, # String with the value of all settings,
# as seen in the Perl configuration file.
CreateTime => "2016-05-29 12:00:01",
CreateBy => 123,
},
# ...
);
=cut
sub ConfigurationDeployList {
my ( $Self, %Param ) = @_;
return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DeploymentListGet();
}
=head2 ConfigurationDeploySync()
Updates C<ZZZAAuto.pm> to the latest deployment found in the database.
my $Success = $SysConfigObject->ConfigurationDeploySync();
=cut
sub ConfigurationDeploySync {
my ( $Self, %Param ) = @_;
my $Home = $Self->{Home};
my $TargetPath = "$Home/Kernel/Config/Files/ZZZAAuto.pm";
if ( -e $TargetPath ) {
if ( !require $TargetPath ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not load $TargetPath, $1",
);
return;
}
do $TargetPath;
}
$Kernel::OM->ObjectsDiscard(
Objects => [ 'Kernel::Config', ],
);
my $CurrentDeploymentID = $Kernel::OM->Get('Kernel::Config')->Get('CurrentDeploymentID') || 0;
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
# Check that all deployments are valid, but wait if there are deployments in add procedure
my $CleanupSuccess;
TRY:
for my $Try ( 1 .. 40 ) {
$CleanupSuccess = $SysConfigDBObject->DeploymentListCleanup();
last TRY if !$CleanupSuccess;
last TRY if $CleanupSuccess == 1;
sleep .5;
}
if ( $CleanupSuccess != 1 ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "There are invalid deployments in the database that could not be removed!",
);
return;
}
my %LastDeployment = $SysConfigDBObject->DeploymentGetLast();
if ( !%LastDeployment ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No deployments found in Database!",
);
return;
}
if ( $CurrentDeploymentID ne $LastDeployment{DeploymentID} ) {
# Write latest deployment to ZZZAAuto.pm
my $EffectiveValueStrg = $LastDeployment{EffectiveValueStrg};
my $Success = $Self->_FileWriteAtomic(
Filename => $TargetPath,
Content => \$EffectiveValueStrg,
);
return if !$Success;
}
# Sync also user specific settings (if available).
return 1 if !$Self->can('UserConfigurationDeploySync'); # OTRS Business Solution™
$Self->UserConfigurationDeploySync();
return 1;
}
=head2 ConfigurationDeployCleanup()
Cleanup old deployments from the database.
my $Success = $SysConfigObject->ConfigurationDeployCleanup();
Returns:
$Success = 1; # or false in case of an error
=cut
sub ConfigurationDeployCleanup {
my ( $Self, %Param ) = @_;
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my @List = $SysConfigDBObject->DeploymentListGet();
my @ListIDs = map { $_->{DeploymentID} } @List;
my $RemainingDeploments = $Kernel::OM->Get('Kernel::Config')->Get('SystemConfiguration::MaximumDeployments') // 20;
@ListIDs = splice( @ListIDs, $RemainingDeploments );
DEPLOYMENT:
for my $DeploymentID (@ListIDs) {
my $Success = $SysConfigDBObject->DeploymentDelete(
DeploymentID => $DeploymentID,
);
if ( !$Success ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "Was not possible to delete deployment $DeploymentID!",
);
next DEPLOYMENT;
}
}
return 1;
}
=head2 ConfigurationDeployGet()
Wrapper of Kernel::System::SysConfig::DB::DeploymentGet() - Get deployment information.
my %Deployment = $SysConfigObject->ConfigurationDeployGet(
DeploymentID => 123,
);
Returns:
%Deployment = (
DeploymentID => 123,
Comments => 'Some Comments',
EffectiveValueStrg => $SettingEffectiveValues, # String with the value of all settings,
# as seen in the Perl configuration file.
CreateTime => "2016-05-29 11:04:04",
CreateBy => 123,
);
=cut
sub ConfigurationDeployGet {
my ( $Self, %Param ) = @_;
return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DeploymentGet(%Param);
}
=head2 ConfigurationDeployGetLast()
Wrapper of Kernel::System::SysConfig::DBDeploymentGetLast() - Get last deployment information.
my %Deployment = $SysConfigObject->ConfigurationDeployGetLast();
Returns:
%Deployment = (
DeploymentID => 123,
Comments => 'Some Comments',
EffectiveValueStrg => $SettingEffectiveValues, # String with the value of all settings,
# as seen in the Perl configuration file.
CreateTime => "2016-05-29 11:04:04",
CreateBy => 123,
);
=cut
sub ConfigurationDeployGetLast {
my ( $Self, %Param ) = @_;
return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DeploymentGetLast();
}
=head2 ConfigurationDeploySettingsListGet()
Gets full modified settings information contained on a given deployment.
my @List = $SysConfigObject->ConfigurationDeploySettingsListGet(
DeploymentID => 123,
);
Returns:
@List = (
{
DefaultID => 123,
ModifiedID => 456,
ModifiedVersionID => 789,
Name => "ProductName",
Description => "Defines the name of the application ...",
Navigation => "ASimple::Path::Structure",
IsInvisible => 1,
IsReadonly => 0,
IsRequired => 1,
IsValid => 1,
HasConfigLevel => 200,
UserModificationPossible => 0, # 1 or 0
XMLContentRaw => "The XML structure as it is on the config file",
XMLContentParsed => "XML parsed to Perl",
EffectiveValue => "Product 6",
DefaultValue => "Product 5",
IsModified => 1, # 1 or 0
IsDirty => 1, # 1 or 0
ExclusiveLockGUID => 'A32CHARACTERLONGSTRINGFORLOCKING',
ExclusiveLockUserID => 1,
ExclusiveLockExpiryTime => '2016-05-29 11:09:04',
CreateTime => "2016-05-29 11:04:04",
ChangeTime => "2016-05-29 11:04:04",
},
{
DefaultID => 321,
ModifiedID => 654,
ModifiedVersionID => 987,
Name => 'FieldName',
# ...
CreateTime => '2010-09-11 10:08:00',
ChangeTime => '2011-01-01 01:01:01',
},
# ...
);
=cut
sub ConfigurationDeploySettingsListGet {
my ( $Self, %Param ) = @_;
if ( !$Param{DeploymentID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need DeploymentID",
);
return;
}
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
# get modified version of this deployment
my %ModifiedVersionList = $SysConfigDBObject->DeploymentModifiedVersionList(
DeploymentID => $Param{DeploymentID},
);
my @ModifiedVersions = sort keys %ModifiedVersionList;
my @Settings;
for my $ModifiedVersionID ( sort @ModifiedVersions ) {
my %Versions;
# Get the modified version.
my %ModifiedSettingVersion = $SysConfigDBObject->ModifiedSettingVersionGet(
ModifiedVersionID => $ModifiedVersionID,
);
# Get default version.
my %DefaultSetting = $SysConfigDBObject->DefaultSettingVersionGet(
DefaultVersionID => $ModifiedSettingVersion{DefaultVersionID},
);
# Update default setting attributes.
for my $Attribute (
qw(ModifiedID IsValid EffectiveValue IsDirty CreateTime ChangeTime)
)
{
$DefaultSetting{$Attribute} = $ModifiedSettingVersion{$Attribute};
}
$DefaultSetting{ModifiedVersionID} = $ModifiedVersionID;
push @Settings, \%DefaultSetting;
}
return @Settings;
}
=head2 ConfigurationIsDirtyCheck()
Check if there are not deployed changes on system configuration.
my $Result = $SysConfigObject->ConfigurationIsDirtyCheck(
UserID => 123, # optional, the user that changes a modified setting
);
Returns:
$Result = 1; # or 0 if configuration is not dirty.
=cut
sub ConfigurationIsDirtyCheck {
my ( $Self, %Param ) = @_;
return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->ConfigurationIsDirty(%Param);
}
=head2 ConfigurationDump()
Creates a YAML file with the system configuration settings.
my $ConfigurationDumpYAML = $SysConfigObject->ConfigurationDump(
OnlyValues => 0, # optional, default 0, dumps only the setting effective value instead of the whole setting attributes.
SkipDefaultSettings => 0, # optional, default 0, do not include default settings
SkipModifiedSettings => 0, # optional, default 0, do not include modified settings
SkipUserSettings => 0, # optional, default 0, do not include user settings
DeploymentID => 123, # optional, if it is provided the modified settings are retrieved from versions
);
Returns:
my $ConfigurationDumpYAML = '---
Default:
Setting1:
DefaultID: 23766
Name: Setting1
# ...
Setting2:
# ...
Modified:
Setting1
DefaultID: 23776
ModifiedID: 1250
Name: Setting1
# ...
# ...
JDoe:
Setting2
DefaultID: 23777
ModifiedID: 1251
Name: Setting2
# ...
# ...
# ...
or
my $ConfigurationDumpYAML = $SysConfigObject->ConfigurationDump(
OnlyValues => 1,
);
Returns:
my $ConfigurationDumpYAML = '---
Default:
Setting1: Test
Setting2: Test
# ...
Modified:
Setting1: TestUpdate
# ...
JDoe:
Setting2: TestUser
# ...
# ...
';
=cut
sub ConfigurationDump {
my ( $Self, %Param ) = @_;
my $Result = {};
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
if ( !$Param{SkipDefaultSettings} ) {
my @SettingsList = $SysConfigDBObject->DefaultSettingListGet();
SETTING:
for my $Setting (@SettingsList) {
if ( $Param{OnlyValues} ) {
$Result->{Default}->{ $Setting->{Name} } = $Setting->{EffectiveValue};
next SETTING;
}
$Result->{Default}->{ $Setting->{Name} } = $Setting;
}
}
if ( !$Param{SkipModifiedSettings} || !$Param{SkipUserSettings} ) {
my @SettingsList;
if ( !$Param{DeploymentID} ) {
@SettingsList = $SysConfigDBObject->ModifiedSettingListGet();
}
else {
# Get the modified versions involved into the deployment
my %ModifiedVersionList = $SysConfigDBObject->DeploymentModifiedVersionList(
DeploymentID => $Param{DeploymentID},
);
return if !%ModifiedVersionList;
my @ModifiedVersions = sort keys %ModifiedVersionList;
MODIFIEDVERSIONID:
for my $ModifiedVersionID (@ModifiedVersions) {
my %ModifiedSettingVersion = $SysConfigDBObject->ModifiedSettingVersionGet(
ModifiedVersionID => $ModifiedVersionID,
);
next MODIFIEDVERSIONID if !%ModifiedSettingVersion;
push @SettingsList, \%ModifiedSettingVersion;
}
}
if ( !$Param{SkipModifiedSettings} ) {
SETTING:
for my $Setting (@SettingsList) {
next SETTING if $Setting->{TargetUserID};
if ( $Param{OnlyValues} ) {
$Result->{'Modified'}->{ $Setting->{Name} } = $Setting->{EffectiveValue};
next SETTING;
}
$Result->{'Modified'}->{ $Setting->{Name} } = $Setting;
}
}
if ( !$Param{SkipUserSettings} && $Self->can('UserConfigurationDump') ) { # OTRS Business Solution™
my %UserSettings = $Self->UserConfigurationDump(
SettingList => \@SettingsList,
OnlyValues => $Param{OnlyValues},
);
if ( scalar keys %UserSettings ) {
%{$Result} = ( %{$Result}, %UserSettings );
}
}
}
my $YAMLString = $Kernel::OM->Get('Kernel::System::YAML')->Dump(
Data => $Result,
);
return $YAMLString;
}
=head2 ConfigurationLoad()
Takes a YAML file with settings definition and try to import it into the system.
my $Success = $SysConfigObject->ConfigurationLoad(
ConfigurationYAML => $YAMLString, # a YAML string in the format of L<ConfigurationDump()>
UserID => 123,
);
=cut
sub ConfigurationLoad {
my ( $Self, %Param ) = @_;
for my $Needed (qw(ConfigurationYAML UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my %ConfigurationRaw
= %{ $Kernel::OM->Get('Kernel::System::YAML')->Load( Data => $Param{ConfigurationYAML} ) || {} };
if ( !%ConfigurationRaw ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "ConfigurationYAML is invalid!",
);
return;
}
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
# Get the configuration sections to import (skip Default and non existing users).
my $ValidSections;
my %Configuration;
SECTION:
for my $Section ( sort keys %ConfigurationRaw ) {
next SECTION if $Section eq 'Default';
if ( $Section eq 'Modified' ) {
$Configuration{$Section} = $ConfigurationRaw{$Section};
next SECTION;
}
my $UserID = $UserObject->UserLookup(
UserLogin => $Section,
Silent => 1,
);
if ( !$UserID ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'notice',
Message => "Settings for user $Section could not be added! User does not exists.",
);
next SECTION;
}
$Configuration{$UserID} = $ConfigurationRaw{$Section};
}
# Early return if there is nothing to update.
return 1 if !%Configuration;
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my $Result = 1;
SECTION:
for my $Section ( sort keys %Configuration ) {
my $UserID = '';
my $ScopeString = '(global)';
my $TargetUserID = undef;
if ( lc $Section ne lc 'Modified' ) {
$TargetUserID = $Section;
$UserID = $Section;
$ScopeString = "(for user $Section)";
}
SETTINGNAME:
for my $SettingName ( sort keys %{ $Configuration{$Section} } ) {
my %CurrentSetting = $Self->SettingGet(
Name => $SettingName,
);
# Set error in case non existing settings (either default or modified);
if ( !%CurrentSetting ) {
$Result = '-1';
next SETTINGNAME;
}
my $ExclusiveLockGUID = $SysConfigDBObject->DefaultSettingLock(
Name => $SettingName,
Force => 1,
UserID => $UserID || $Param{UserID},
);
my $UserModificationActive = $TargetUserID ? undef : $CurrentSetting{UserModificationActive};
my %Result = $Self->SettingUpdate(
Name => $SettingName,
IsValid => $Configuration{$Section}->{$SettingName}->{IsValid},
EffectiveValue => $Configuration{$Section}->{$SettingName}->{EffectiveValue},
UserModificationActive => $UserModificationActive,
TargetUserID => $TargetUserID,
ExclusiveLockGUID => $ExclusiveLockGUID,
UserID => $UserID || $Param{UserID},
);
if ( !$Result{Success} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Setting $SettingName $ScopeString was not correctly updated!",
);
$Result = '-1';
}
}
# Only deploy user specific settings;
next SECTION if !$TargetUserID;
next SECTION if !$Self->can('UserConfigurationDeploy'); # OTRS Business Solution™
# Deploy user configuration requires another package to be installed.
my $Success = $Self->UserConfigurationDeploy(
TargetUserID => $TargetUserID,
UserID => $Param{UserID},
);
}
return $Result;
}
=head2 ConfigurationDirtySettingsList()
Returns a list of setting names that are dirty.
my @Result = $SysConfigObject->ConfigurationDirtySettingsList(
ChangeBy => 123,
);
Returns:
$Result = ['SettingA', 'SettingB', 'SettingC'];
=cut
sub ConfigurationDirtySettingsList {
my ( $Self, %Param ) = @_;
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my @DefaultSettingsList = $SysConfigDBObject->DefaultSettingListGet(
IsDirty => 1,
);
@DefaultSettingsList = map { $_->{Name} } @DefaultSettingsList;
my @ModifiedSettingsList = $SysConfigDBObject->ModifiedSettingListGet(
IsDirty => 1,
IsGlobal => 1,
ChangeBy => $Param{ChangeBy} || undef,
);
@ModifiedSettingsList = map { $_->{Name} } @ModifiedSettingsList;
# Combine Default and Modified dirty settings.
my @ListNames = ( @DefaultSettingsList, @ModifiedSettingsList );
my %Names = map { $_ => 1 } @ListNames;
@ListNames = sort keys %Names;
return @ListNames;
}
=head2 ConfigurationLockedSettingsList()
Returns a list of setting names that are locked in general or by user.
my @Result = $SysConfigObject->ConfigurationLockedSettingsList(
ExclusiveLockUserID => 2, # Optional, ID of the user for which the default setting is locked
);
Returns:
$Result = ['SettingA', 'SettingB', 'SettingC'];
=cut
sub ConfigurationLockedSettingsList {
my ( $Self, %Param ) = @_;
my @DefaultSettingsList = $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingListGet(
Locked => 1,
);
return if !IsArrayRefWithData( \@DefaultSettingsList );
if ( $Param{ExclusiveLockUserID} ) {
@DefaultSettingsList
= map { $_->{Name} } grep { $_->{ExclusiveLockUserID} eq $Param{ExclusiveLockUserID} } @DefaultSettingsList;
}
else {
@DefaultSettingsList = map { $_->{Name} } @DefaultSettingsList;
}
return @DefaultSettingsList;
}
=head2 ConfigurationSearch()
Returns a list of setting names.
my @Result = $SysConfigObject->ConfigurationSearch(
Search => 'The search string', # (optional)
Category => 'OTRS' # (optional)
IncludeInvisible => 1, # (optional) Default 0.
);
Returns:
$Result = ['SettingA', 'SettingB', 'SettingC'];
=cut
sub ConfigurationSearch {
my ( $Self, %Param ) = @_;
if ( !$Param{Search} && !$Param{Category} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Search or Category is needed",
);
return;
}
$Param{Search} ||= '';
$Param{Category} ||= '';
my $Search = lc $Param{Search};
my %Settings = $Self->ConfigurationTranslatedGet(
IncludeInvisible => $Param{IncludeInvisible},
);
my %Result;
SETTING:
for my $SettingName ( sort keys %Settings ) {
# check category
if (
$Param{Category} &&
$Param{Category} ne 'All' &&
$Settings{$SettingName}->{Category} &&
$Settings{$SettingName}->{Category} ne $Param{Category}
)
{
next SETTING;
}
# check invisible
if (
!$Param{IncludeInvisible}
&& $Settings{$SettingName}->{IsInvisible}
)
{
next SETTING;
}
if ( !$Param{Search} ) {
$Result{$SettingName} = 1;
next SETTING;
}
$Param{Search} =~ s{ +}{ }g;
my @SearchTerms = split ' ', $Param{Search};
SEARCHTERM:
for my $SearchTerm (@SearchTerms) {
# do not search with the x and/or g modifier as it would produce wrong search results!
if ( $Settings{$SettingName}->{Metadata} =~ m{\Q$SearchTerm\E}msi ) {
next SEARCHTERM if $Result{$SettingName};
$Result{$SettingName} = 1;
}
}
}
return ( sort keys %Result );
}
=head2 ConfigurationCategoriesGet()
Returns a list of categories with their filenames.
my %Categories = $SysConfigObject->ConfigurationCategoriesGet();
Returns:
%Categories = (
All => {
DisplayName => 'All Settings',
Files => [],
},
OTRS => {
DisplayName => 'OTRS',
Files => ['Calendar.xml', CloudServices.xml', 'Daemon.xml', 'Framework.xml', 'GenericInterface.xml', 'ProcessManagement.xml', 'Ticket.xml' ],
},
# ...
);
=cut
sub ConfigurationCategoriesGet {
my ( $Self, %Param ) = @_;
my $CacheType = 'SysConfig';
my $CacheKey = 'ConfigurationCategoriesGet';
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# Return cache.
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
return %{$Cache} if ref $Cache eq 'HASH';
# Set framework files.
my %Result = (
All => {
DisplayName => Translatable('All Settings'),
Files => [],
},
OTRS => {
DisplayName => 'OTRS',
Files => [
'Calendar.xml', 'CloudServices.xml', 'Daemon.xml', 'Framework.xml',
'GenericInterface.xml', 'ProcessManagement.xml', 'Ticket.xml',
],
},
);
my @PackageList = $Kernel::OM->Get('Kernel::System::Package')->RepositoryList();
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# Get files from installed packages.
PACKAGE:
for my $Package (@PackageList) {
next PACKAGE if !$Package->{Name}->{Content};
next PACKAGE if !IsArrayRefWithData( $Package->{Filelist} );
my @XMLFiles;
FILE:
for my $File ( @{ $Package->{Filelist} } ) {
$File->{Location} =~ s/\/\//\//g;
my $Search = 'Kernel/Config/Files/XML/';
if ( substr( $File->{Location}, 0, length $Search ) ne $Search ) {
next FILE;
}
my $Filename = $File->{Location};
$Filename =~ s{\AKernel/Config/Files/XML/(.+\.xml)\z}{$1}msxi;
push @XMLFiles, $Filename;
}
next PACKAGE if !@XMLFiles;
my $PackageName = $Package->{Name}->{Content};
my $DisplayName = $ConfigObject->Get("SystemConfiguration::Category::Name::$PackageName") || $PackageName;
# special treatment for OTRS Business Solution™
if ( $DisplayName eq 'OTRSBusiness' ) {
$DisplayName = 'OTRS Business Solution™';
}
$Result{$PackageName} = {
DisplayName => $DisplayName,
Files => \@XMLFiles,
};
}
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \%Result,
TTL => 24 * 3600 * 30, # 1 month
);
return %Result;
}
=head2 ForbiddenValueTypesGet()
Returns a hash of forbidden value types.
my %ForbiddenValueTypes = $SysConfigObject->ForbiddenValueTypesGet();
Returns:
%ForbiddenValueType = (
String => [],
Select => ['Option'],
...
);
=cut
sub ForbiddenValueTypesGet {
my ( $Self, %Param ) = @_;
my $CacheType = 'SysConfig';
my $CacheKey = 'ForbiddenValueTypesGet';
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# Return cache.
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
return %{$Cache} if ref $Cache eq 'HASH';
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my @ValueTypes = $Self->_ValueTypesList();
my %Result;
for my $ValueType (@ValueTypes) {
my $Loaded = $MainObject->Require(
"Kernel::System::SysConfig::ValueType::$ValueType",
);
if ($Loaded) {
my $ValueTypeObject = $Kernel::OM->Get(
"Kernel::System::SysConfig::ValueType::$ValueType",
);
my @ForbiddenValueTypes = $ValueTypeObject->ForbiddenValueTypes();
if ( scalar @ForbiddenValueTypes ) {
$Result{$ValueType} = \@ForbiddenValueTypes;
}
}
}
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \%Result,
TTL => 24 * 3600, # 1 day
);
return %Result;
}
=head2 ValueAttributeList()
Returns a hash of forbidden value types.
my @ValueAttributeList = $SysConfigObject->ValueAttributeList();
Returns:
@ValueAttributeList = (
"Content",
"SelectedID",
);
=cut
sub ValueAttributeList {
my ( $Self, %Param ) = @_;
my $CacheType = 'SysConfig';
my $CacheKey = 'ValueAttributeList';
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# Return cache.
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
return @{$Cache} if ref $Cache eq 'ARRAY';
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my @ValueTypes = $Self->_ValueTypesList();
my @Result;
for my $ValueType (@ValueTypes) {
my $Loaded = $MainObject->Require(
"Kernel::System::SysConfig::ValueType::$ValueType",
);
if ($Loaded) {
my $ValueTypeObject = $Kernel::OM->Get(
"Kernel::System::SysConfig::ValueType::$ValueType",
);
my $ValueAttribute = $ValueTypeObject->ValueAttributeGet();
if ( !grep { $_ eq $ValueAttribute } @Result ) {
push @Result, $ValueAttribute;
}
}
}
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \@Result,
TTL => 24 * 3600, # 1 day
);
return @Result;
}
=head2 SettingsSet()
This method locks provided settings(by force), updates them and deploys the changes (by force).
my $Success = $SysConfigObject->SettingsSet(
UserID => 1, # (required) UserID
Comments => 'Deployment comment', # (optional) Comment
Settings => [ # (required) List of settings to update.
{
Name => 'Setting::Name', # (required)
EffectiveValue => 'Value', # (optional)
IsValid => 1, # (optional)
UserModificationActive => 1, # (optional)
},
...
],
);
Returns:
$Success = 1;
=cut
sub SettingsSet {
my ( $Self, %Param ) = @_;
if ( !$Param{UserID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Needed UserID!"
);
return;
}
if ( !IsArrayRefWithData( $Param{Settings} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Settings must be array with data!"
);
return;
}
my @DeploySettings;
for my $Setting ( @{ $Param{Settings} } ) {
my $ExclusiveLockGUID = $Self->SettingLock(
Name => $Setting->{Name},
Force => 1,
UserID => $Param{UserID},
);
return if !$ExclusiveLockGUID;
my %UpdateResult = $Self->SettingUpdate(
%{$Setting},
ExclusiveLockGUID => $ExclusiveLockGUID,
UserID => $Param{UserID},
);
if ( $UpdateResult{Success} ) {
push @DeploySettings, $Setting->{Name};
}
}
# Deploy successfully updated settings.
my %DeploymentResult = $Self->ConfigurationDeploy(
Comments => $Param{Comments} || '',
UserID => $Param{UserID},
Force => 1,
DirtySettings => \@DeploySettings,
);
return $DeploymentResult{Success};
}
=head2 OverriddenFileNameGet()
Returns file name which overrides setting Effective value.
my $FileName = $SysConfigObject->OverriddenFileNameGet(
SettingName => 'Setting::Name', # (required)
UserID => 1, # (required)
EffectiveValue => '3', # (optional) EffectiveValue stored in the DB.
);
Returns:
$FileName = 'ZZZUpdate.pm';
=cut
sub OverriddenFileNameGet {
my ( $Self, %Param ) = @_;
# Check needed stuff.
for my $Needed (qw(SettingName UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $LoadedEffectiveValue = $Self->GlobalEffectiveValueGet(
SettingName => $Param{SettingName},
);
my @SettingStructure = split( '###', $Param{SettingName} );
my $EffectiveValue = $Param{EffectiveValue};
# Replace config variables in effective values.
# NOTE: First level only, make sure to update this code once same mechanism has been improved in Defaults.pm.
# Please see bug#12916 and bug#13376 for more information.
$EffectiveValue =~ s/\<OTRS_CONFIG_(.+?)\>/$ConfigObject->{$1}/g;
my $IsOverridden = DataIsDifferent(
Data1 => $EffectiveValue // {},
Data2 => $LoadedEffectiveValue // {},
);
# This setting is not Overridden in perl file, return.
return if !$IsOverridden;
my $Result;
my $Home = $ConfigObject->Get('Home');
my $Directory = "$Home/Kernel/Config/Files";
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
# Get all .pm files that start with 'ZZZ'.
my @FilesInDirectory = $MainObject->DirectoryRead(
Directory => $Directory,
Filter => 'ZZZ*.pm',
);
my @Modules;
FILE:
for my $File (@FilesInDirectory) {
# Get only file name, without full path and extension.
$File =~ m{^.*/(.*?)\.pm$};
my $FileName = $1;
# Skip the file that was regulary deployed.
next FILE if $FileName eq 'ZZZAAuto';
push @Modules, {
"Kernel::Config::Files::$FileName" => $File,
};
}
# Check Config.pm as well.
push @Modules, {
'Kernel::Config' => 'Kernel/Config.pm',
};
# Read EffectiveValues from DB (they are stored in ZZZAAuto file).
my $Loaded = $MainObject->Require(
'Kernel::Config::Files::ZZZAAuto',
Silent => 1,
);
# If module couldn't be loaded, there is no user specific setting.
return if !$Loaded;
my $ConfigFromDB = {};
Kernel::Config::Files::ZZZAAuto->Load($ConfigFromDB);
for my $Module (@Modules) {
my $ModuleName = ( keys %{$Module} )[0];
# Check if this module overrides our setting.
my $SettingFound = $Self->_IsOverriddenInModule(
Module => $ModuleName,
SettingStructure => \@SettingStructure,
ConfigFromDB => $ConfigFromDB,
);
if ($SettingFound) {
$Result = $Module->{$ModuleName};
}
}
if ($Result) {
$Result =~ s/^$Home\/?(.*)$/$1/;
}
return $Result;
}
=head2 GlobalEffectiveValueGet()
Returns global effective value for provided setting name.
my $EffectiveValue = $SysConfigObject->GlobalEffectiveValueGet(
SettingName => 'Setting::Name', # (required)
);
Returns:
$EffectiveValue = 'test';
=cut
sub GlobalEffectiveValueGet {
my ( $Self, %Param ) = @_;
# Check needed stuff.
if ( !$Param{SettingName} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need SettingName!",
);
return;
}
my $GlobalConfigObject = Kernel::Config->new();
my $LoadedEffectiveValue;
my @SettingStructure = split( '###', $Param{SettingName} );
for my $Key (@SettingStructure) {
if ( !defined $LoadedEffectiveValue ) {
# first iteration
$LoadedEffectiveValue = $GlobalConfigObject->Get($Key);
}
elsif ( ref $LoadedEffectiveValue eq 'HASH' ) {
$LoadedEffectiveValue = $LoadedEffectiveValue->{$Key};
}
}
return $LoadedEffectiveValue;
}
=head1 PRIVATE INTERFACE
=head2 _IsOverriddenInModule()
Helper method to check if setting is overridden in specific module.
my $Overridden = $SysConfigObject->_IsOverriddenInModule(
Module => "Kernel::Config::Files::ZZZAAuto",
SettingStructure => [ 'DashboardBackend', '0000-ProductNotify' ],
LoadedEffectiveValue => 'Value',
);
=cut
sub _IsOverriddenInModule {
my ( $Self, %Param ) = @_;
# Check needed stuff.
for my $Needed (qw(Module SettingStructure ConfigFromDB)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
if ( !IsArrayRefWithData( $Param{SettingStructure} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "SettingStructure must be an array!"
);
return;
}
my $Result;
my $Loaded = $Kernel::OM->Get('Kernel::System::Main')->Require(
$Param{Module},
Silent => 1,
);
# If module couldn't be loaded, there is no user specific setting.
return $Result if !$Loaded;
# Get effective value from the DB.
my $OverriddenSettings = $Kernel::OM->Get('Kernel::System::Storable')->Clone(
Data => $Param{ConfigFromDB},
);
if ( $Param{Module} eq 'Kernel::Config' ) {
bless( $OverriddenSettings, 'Kernel::Config' );
$OverriddenSettings->Load();
}
else {
# Apply changes from this file only.
$Param{Module}->Load($OverriddenSettings);
}
# OverridenSettings contains EffectiveValues from DB, overridden by provided Module,
# so we can compare if setting was changed in this file.
# Loaded hash is empty, return.
return $Result if !IsHashRefWithData($OverriddenSettings) && ref $OverriddenSettings ne 'Kernel::Config';
# Check if this file overrides our setting.
my $SettingFound = 0;
my $LoadedEffectiveValue;
my $ConfigFromDB;
KEY:
for my $Key ( @{ $Param{SettingStructure} } ) {
if ( !defined $LoadedEffectiveValue ) {
# First iteration.
$LoadedEffectiveValue = $OverriddenSettings->{$Key};
$ConfigFromDB = $Param{ConfigFromDB}->{$Key};
if ( defined $ConfigFromDB && !defined $LoadedEffectiveValue ) {
# Setting is overridden using the "delete" statement.
$SettingFound = 1;
}
elsif (
DataIsDifferent(
Data1 => $LoadedEffectiveValue // {},
Data2 => $ConfigFromDB // {},
)
)
{
$SettingFound = 1;
}
else {
last KEY;
}
}
elsif ( ref $LoadedEffectiveValue eq 'HASH' ) {
$LoadedEffectiveValue = $LoadedEffectiveValue->{$Key};
$ConfigFromDB = $ConfigFromDB->{$Key};
if ( defined $ConfigFromDB && !defined $LoadedEffectiveValue ) {
# Setting is overridden using the "delete" statement.
$SettingFound = 1;
}
elsif (
DataIsDifferent(
Data1 => $LoadedEffectiveValue // {},
Data2 => $ConfigFromDB // {},
)
)
{
$SettingFound = 1;
}
else {
$SettingFound = 0;
}
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Unhandled exception!"
);
}
}
return $SettingFound;
}
=head2 _FileWriteAtomic()
Writes a file in an atomic operation. This is achieved by creating
a temporary file, filling and renaming it. This avoids inconsistent states
when the file is updated.
my $Success = $SysConfigObject->_FileWriteAtomic(
Filename => "$Self->{Home}/Kernel/Config/Files/ZZZAAuto.pm",
Content => \$NewContent,
);
=cut
sub _FileWriteAtomic {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Filename Content)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!"
);
return;
}
}
my $TempFilename = $Param{Filename} . '.' . $$;
my $FH;
## no critic
if ( !open( $FH, ">$Self->{FileMode}", $TempFilename ) ) {
## use critic
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Can't open file $TempFilename: $!",
);
return;
}
print $FH ${ $Param{Content} };
close $FH;
if ( !rename $TempFilename, $Param{Filename} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not rename $TempFilename to $Param{Filename}: $!"
);
return;
}
return 1;
}
=head2 _ConfigurationTranslatableStrings()
Gathers strings marked as translatable from a setting XML parsed content and saves it on
ConfigurationTranslatableStrings global variable.
$SysConfigObject->_ConfigurationTranslatableStrings(
Data => $Data, # could be SCALAR, ARRAY or HASH
);
=cut
sub _ConfigurationTranslatableStrings {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Data)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!"
);
return;
}
}
# Start recursion if its an array.
if ( ref $Param{Data} eq 'ARRAY' ) {
KEY:
for my $Key ( @{ $Param{Data} } ) {
next KEY if !$Key;
$Self->_ConfigurationTranslatableStrings( Data => $Key );
}
return;
}
# Start recursion if its a Hash.
if ( ref $Param{Data} eq 'HASH' ) {
for my $Key ( sort keys %{ $Param{Data} } ) {
if (
ref $Param{Data}->{$Key} eq ''
&& $Param{Data}->{Translatable}
&& $Param{Data}->{Content}
)
{
return if !$Param{Data}->{Content};
return if $Param{Data}->{Content} =~ /^\d+$/;
$Self->{ConfigurationTranslatableStrings}->{ $Param{Data}->{Content} } = 1;
}
$Self->_ConfigurationTranslatableStrings( Data => $Param{Data}->{$Key} );
}
}
return;
}
=head2 _DBCleanUp();
Removes all settings defined in the database (including default and modified) that are not included
in the settings parameter
my $Success = $SysConfigObject->_DBCleanUp(
Settings => {
'ACL::CacheTTL' => {
XMLContentParsed => '
<Setting Name="SettingName" Required="1" Valid="1">
<Description Translatable="1">Test.</Description>
# ...
</Setting>',
XMLContentRaw => {
Description => [
{
Content => 'Test.',
Translatable => '1',
},
],
Name => 'Test',
# ...
},
# ...
};
);
Returns:
$Success = 1; # or false in case of a failure
=cut
sub _DBCleanUp {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Settings)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!"
);
return;
}
}
if ( !IsHashRefWithData( $Param{Settings} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Settings must be an HashRef!"
);
return;
}
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my @SettingsDB = $SysConfigDBObject->DefaultSettingList();
my ( $DefaultUpdated, $ModifiedUpdated );
for my $SettingDB (@SettingsDB) {
# Cleanup database if the setting is not present in the XML files.
if ( !$Param{Settings}->{ $SettingDB->{Name} } ) {
# Get all modified settings.
my @ModifiedSettings = $SysConfigDBObject->ModifiedSettingListGet(
Name => $SettingDB->{Name},
);
for my $ModifiedSetting (@ModifiedSettings) {
# Delete from modified table.
my $SuccessDeleteModified = $SysConfigDBObject->ModifiedSettingDelete(
ModifiedID => $ModifiedSetting->{ModifiedID},
);
if ( !$SuccessDeleteModified ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "System couldn't delete $SettingDB->{Name} from DB (sysconfig_modified)!"
);
}
}
my @ModifiedSettingVersions = $SysConfigDBObject->ModifiedSettingVersionListGet(
Name => $SettingDB->{Name},
);
for my $ModifiedSettingVersion (@ModifiedSettingVersions) {
# Delete from modified table.
my $SuccessDeleteModifiedVersion = $SysConfigDBObject->ModifiedSettingVersionDelete(
ModifiedVersionID => $ModifiedSettingVersion->{ModifiedVersionID},
);
if ( !$SuccessDeleteModifiedVersion ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "System couldn't delete $SettingDB->{Name} from DB (sysconfig_modified_version)!"
);
}
}
# Delete from default table.
my $SuccessDefaultSetting = $SysConfigDBObject->DefaultSettingDelete(
Name => $SettingDB->{Name},
);
if ( !$SuccessDefaultSetting ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "System couldn't delete $SettingDB->{Name} from DB (sysconfig_default)!"
);
}
}
}
return 1;
}
=head2 _NavigationTree();
Returns navigation as a tree (in a hash).
my %Result = $SysConfigObject->_NavigationTree(
'Array' => [ # Array of setting navigation items
'Core',
'Core::CustomerUser',
'Frontend',
],
'Tree' => { # Result from previous recursive call
'Core' => {
'Core::CustomerUser' => {},
},
},
);
Returns:
%Result = (
'Core' => {
'Core::CustomerUser' => {},
},
'Frontend' => {},
);
=cut
sub _NavigationTree {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Tree Array)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my %Result = %{ $Param{Tree} };
return %Result if !IsArrayRefWithData( $Param{Array} );
# Check if first item exists.
if ( !defined $Result{ $Param{Array}->[0] } ) {
$Result{ $Param{Array}->[0] } = {
Subitems => {},
};
}
# Check if it's deeper tree.
if ( scalar @{ $Param{Array} } > 1 ) {
my @SubArray = splice( @{ $Param{Array} }, 1 );
my %Hash = $Self->_NavigationTree(
Tree => $Result{ $Param{Array}->[0] }->{Subitems},
Array => \@SubArray,
);
if (%Hash) {
$Result{ $Param{Array}->[0] } = {
Subitems => \%Hash,
};
}
}
return %Result;
}
sub _NavigationTreeNodeCount {
my ( $Self, %Param ) = @_;
# Check needed stuff.
for my $Needed (qw(Settings)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my %Result = %{ $Param{Tree} // {} };
NODE_NAME:
for my $NodeName ( sort keys %Result ) {
my @Matches = grep { $_->{Navigation} eq $NodeName } @{ $Param{Settings} };
$Result{$NodeName}->{Count} = scalar @Matches;
my %SubResult = $Self->_NavigationTreeNodeCount(
Tree => $Result{$NodeName}->{Subitems},
Settings => $Param{Settings},
);
$Result{$NodeName}->{Subitems} = {
%{ $Result{$NodeName}->{Subitems} },
%SubResult,
};
}
return %Result;
}
=head2 _ConfigurationEntitiesGet();
Returns hash of used entities for provided Setting value.
my %Result = $SysConfigObject->_ConfigurationEntitiesGet(
'Name' => 'Ticket::Frontend::AgentTicketPriority###Entity', # setting name
'Result' => {}, # result from previous recursive call
'Value' => [ # setting Value
{
'Item' => [
{
'Content' => '3 medium',
'ValueEntityType' => 'Priority',
'ValueRegex' => '',
'ValueType' => 'Entity',
},
],
},
],
);
Returns:
%Result = {
'Priority' => {
'3 medium' => [
'Ticket::Frontend::AgentTicketPriority###Entity',
],
},
};
=cut
sub _ConfigurationEntitiesGet {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Value Result Name)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my %Result = %{ $Param{Result} || {} };
my $ValueEntityType = $Param{ValueEntityType} || '';
if ( ref $Param{Value} eq 'ARRAY' ) {
for my $Item ( @{ $Param{Value} } ) {
%Result = $Self->_ConfigurationEntitiesGet(
%Param,
Value => $Item,
Result => \%Result,
);
}
}
elsif ( ref $Param{Value} eq 'HASH' ) {
if ( $Param{Value}->{ValueEntityType} ) {
$ValueEntityType = $Param{Value}->{ValueEntityType};
}
if ( $Param{Value}->{Content} ) {
# If there is no hash item, create new.
if ( !defined $Result{$ValueEntityType} ) {
$Result{$ValueEntityType} = {};
}
# Extract value (without white space).
my $Value = $Param{Value}->{Content};
$Value =~ s{^\s*(.*?)\s*$}{$1}gsmx;
$Value //= '';
# If there is no array, create
if ( !IsArrayRefWithData( $Result{$ValueEntityType}->{$Value} ) ) {
$Result{$ValueEntityType}->{$Value} = [];
}
# Check if current config is not in the array.
if ( !grep { $_ eq $Param{Name} } @{ $Result{$ValueEntityType}->{$Value} } ) {
push @{ $Result{$ValueEntityType}->{$Value} }, $Param{Name};
}
}
else {
for my $Key (qw(Item Hash Array)) {
if ( defined $Param{Value}->{$Key} ) {
# Contains children
%Result = $Self->_ConfigurationEntitiesGet(
%Param,
ValueEntityType => $ValueEntityType,
Value => $Param{Value}->{$Key},
Result => \%Result,
);
}
}
}
}
return %Result;
}
=head2 _EffectiveValues2PerlFile()
Converts effective values from settings into a combined perl hash ready to write into a file.
my $FileString = $SysConfigObject->_EffectiveValues2PerlFile(
Settings => [
{
Name => 'SettingName',
IsValid => 1,
EffectiveValue => $ValueStructure,
},
{
Name => 'AnotherSettingName',
IsValid => 0,
EffectiveValue => $AnotherValueStructure,
},
# ...
],
TargetPath => 'Kernel/Config/Files/ZZZAAuto.pm',
);
=cut
sub _EffectiveValues2PerlFile {
my ( $Self, %Param ) = @_;
for my $Needed (qw(Settings TargetPath)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
if ( !IsArrayRefWithData( $Param{Settings} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Settings parameter is invalid!",
);
return;
}
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my $PerlHashStrg;
my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $CacheType = 'SysConfigPersistent';
my $CacheKey = 'EffectiveValues2PerlFile';
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
) // {};
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $CurrentSystemTime = $DateTimeObject->ToEpoch();
$DateTimeObject->Add(
Months => 1,
);
my $ExpireTime = $DateTimeObject->ToEpoch();
my $CacheDifferent;
# Delete all expired keys.
my @ExpiredKeys = grep { $CurrentSystemTime > $Cache->{$_}->{ExpireTime} } keys %{$Cache};
delete @{$Cache}{@ExpiredKeys};
# If there are expired keys, cache needs to be set to a new value.
$CacheDifferent = scalar @ExpiredKeys ? 1 : 0;
# Convert all settings from DB format to perl file.
for my $Setting ( @{ $Param{Settings} } ) {
my $Name = $Setting->{Name};
$Name =~ s/\\/\\\\/g;
$Name =~ s/'/\'/g;
$Name =~ s/###/'}->{'/g;
if ( $Setting->{IsValid} ) {
my $EffectiveValue;
my $ValueString = $Setting->{EffectiveValue} // '';
if ( ref $ValueString ) {
my $String = $StorableObject->Serialize(
Data => $Setting->{EffectiveValue},
);
$ValueString = $MainObject->MD5sum(
String => \$String,
);
}
if (
$Cache->{$ValueString}
&& $Cache->{$ValueString}->{Value}
)
{
$EffectiveValue = $Cache->{$ValueString}->{Value};
}
else {
$EffectiveValue = $MainObject->Dump( $Setting->{EffectiveValue} );
$Cache->{$ValueString} = {
Value => $EffectiveValue,
ExpireTime => $ExpireTime,
};
# Cache has been changed, it needs to be set.
$CacheDifferent = 1;
}
$EffectiveValue =~ s/\$VAR1 =//;
$PerlHashStrg .= "\$Self->{'$Name'} = $EffectiveValue";
}
elsif ( eval( '$Self->{ConfigDefaultObject}->{\'' . $Name . '\'}' ) ) {
$PerlHashStrg .= "delete \$Self->{'$Name'};\n";
}
}
if ($CacheDifferent) {
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => $Cache,
TTL => 20 * 24 * 60 * 60,
);
}
chomp $PerlHashStrg;
# Convert TartgetPath to Package.
my $TargetPath = $Param{TargetPath};
$TargetPath =~ s{(.*)\.(?:.*)}{$1}msx;
$TargetPath =~ s{ / }{::}msxg;
# Write default config file.
my $FileStrg = <<"EOF";
# OTRS config file (automatically generated)
# VERSION:2.0
package $TargetPath;
use strict;
use warnings;
no warnings 'redefine'; ## no critic
EOF
if ( $Self->{utf8} ) {
$FileStrg .= "use utf8;\n";
}
$FileStrg .= <<"EOF";
sub Load {
my (\$File, \$Self) = \@_;
$PerlHashStrg
return;
}
1;
EOF
return $FileStrg;
}
=head2 _SettingEffectiveValueCheck()
Recursive helper for SettingEffectiveValueCheck().
my %Result = $SysConfigObject->_SettingEffectiveValueCheck(
EffectiveValue => 'open', # (optional) The EffectiveValue to be checked,
# (could be also a complex structure).
XMLContentParsed => { # (required) The XMLContentParsed value from Default Setting.
Value => [
{
'Item' => [
{
'Content' => "Scalar value",
},
],
},
],
},
NoValidation => $Param{NoValidation}, # (optional), skip validation
CurrentSystemTime => 1507894796935, # (optional) Use provided 1507894796935, otherwise calculate
ExpireTime => 1507894896935, # (optional) Use provided ExpireTime for cache, otherwise calculate
UserID => 1, # (required) UserID
);
Returns:
%Result = (
EffectiveValue => 'closed', # Note that EffectiveValue can be changed.
Success => 1, # or false in case of fail
Error => undef, # or error string
);
=cut
sub _SettingEffectiveValueCheck {
my ( $Self, %Param ) = @_;
# Check needed stuff.
for my $Needed (qw(XMLContentParsed UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# So far everything is OK, we need to check deeper (recursive).
my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
my $Default = $StorableObject->Clone(
Data => $Param{XMLContentParsed},
);
my $EffectiveValue = $Param{EffectiveValue};
if ( ref $Param{EffectiveValue} ) {
$EffectiveValue = $StorableObject->Clone(
Data => $Param{EffectiveValue},
);
}
return $Self->SettingEffectiveValueCheck(
XMLContentParsed => $Default,
EffectiveValue => $EffectiveValue,
NoValidation => $Param{NoValidation},
CurrentSystemTime => $Param{CurrentSystemTime},
ExpireTime => $Param{ExpireTime},
UserID => $Param{UserID},
);
}
=head2 _SettingEffectiveValueCheckCacheSet()
Sets cache for EffectiveValueCheck to the provided value.
$SysConfigObject->_SettingEffectiveValueCheckCacheSet(
Value => { (required)
Default180920170714165331 => {
Success => 1,
},
...
},
NoValidation => 0, (optional)
);
=cut
sub _SettingEffectiveValueCheckCacheSet {
my ( $Self, %Param ) = @_;
# Check needed stuff.
for my $Needed (qw(Value)) {
if ( !defined $Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my $CacheType = 'SysConfigPersistent';
my $CacheKey = "EffectiveValueCheck::$Param{NoValidation}";
return $Kernel::OM->Get('Kernel::System::Cache')->Set(
Type => $CacheType,
Key => $CacheKey,
Value => $Param{Value},
TTL => 20 * 24 * 60 * 60,
);
}
=head2 _GetSettingsToDeploy()
Returns the correct list of settings for a deployment taking the settings from different sources:
NotDirty: fetch default settings plus already deployed modified settings.
AllSettings: fetch default settings plus all modified settings already deployed or not.
DirtySettings: fetch default settings plus already deployed settings plus all not deployed settings in the list.
my @SettingList = $SysConfigObject->_GetSettingsToDeploy(
NotDirty => 1, # optional - exclusive (1||0)
All => 1, # optional - exclusive (1||0)
DirtySettings => [ 'SettingName1', 'SettingName2' ], # optional - exclusive
);
@SettingList = (
{
DefaultID => 123,
Name => "ProductName",
Description => "Defines the name of the application ...",
Navigation => "ASimple::Path::Structure",
IsInvisible => 1,
IsReadonly => 0,
IsRequired => 1,
IsValid => 1,
HasConfigLevel => 200,
UserModificationPossible => 0, # 1 or 0
UserModificationActive => 0, # 1 or 0
UserPreferencesGroup => 'Advanced', # optional
XMLContentRaw => "The XML structure as it is on the config file",
XMLContentParsed => "XML parsed to Perl",
EffectiveValue => "Product 6",
DefaultValue => "Product 5",
IsModified => 1, # 1 or 0
IsDirty => 1, # 1 or 0
ExclusiveLockGUID => 'A32CHARACTERLONGSTRINGFORLOCKING',
ExclusiveLockUserID => 1,
ExclusiveLockExpiryTime => '2016-05-29 11:09:04',
CreateTime => "2016-05-29 11:04:04",
ChangeTime => "2016-05-29 11:04:04",
},
{
DefaultID => 321,
Name => 'FieldName',
# ...
CreateTime => '2010-09-11 10:08:00',
ChangeTime => '2011-01-01 01:01:01',
},
# ...
);
=cut
sub _GetSettingsToDeploy {
my ( $Self, %Param ) = @_;
if ( !$Param{NotDirty} && !$Param{DirtySettings} ) {
$Param{AllSettings} = 1;
}
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my @DefaultSettingsList = $SysConfigDBObject->DefaultSettingListGet(
NoCache => $Param{NoCache},
);
# Create a lookup table for the default settings (for easy adding modified).
my %SettingsLookup = map { $_->{Name} => $_ } @DefaultSettingsList;
my @ModifiedSettingsList;
# Use if - else statement, as the gathering of the settings could be expensive.
if ( $Param{NotDirty} ) {
@ModifiedSettingsList = $SysConfigDBObject->ModifiedSettingVersionListGetLast();
}
else {
@ModifiedSettingsList = $SysConfigDBObject->ModifiedSettingListGet(
IsGlobal => 1,
);
}
if ( $Param{AllSettings} || $Param{NotDirty} ) {
# Create a lookup table for the modified settings (for easy merging with defaults).
my %ModifiedSettingsLookup = map { $_->{Name} => $_ } @ModifiedSettingsList;
# Merge modified into defaults.
KEY:
for my $Key ( sort keys %SettingsLookup ) {
next KEY if !$ModifiedSettingsLookup{$Key};
$SettingsLookup{$Key} = {
%{ $SettingsLookup{$Key} },
%{ $ModifiedSettingsLookup{$Key} },
};
}
my @Settings = map { $SettingsLookup{$_} } ( sort keys %SettingsLookup );
return @Settings;
}
my %DirtySettingsLookup = map { $_ => 1 } @{ $Param{DirtySettings} };
SETTING:
for my $Setting (@ModifiedSettingsList) {
my $SettingName = $Setting->{Name};
# Skip invalid settings (all modified needs to have a default).
next SETTING if !$SettingsLookup{$SettingName};
# Remember modified.
my %ModifiedSetting = %{$Setting};
# If setting is not in the given list, then do not use current value but last deployed.
if ( $Setting->{IsDirty} && !$DirtySettingsLookup{$SettingName} ) {
%ModifiedSetting = $SysConfigDBObject->ModifiedSettingVersionGetLast(
Name => $Setting->{Name},
);
# If there is not previous version then skip to keep the default intact.
next SETTING if !%ModifiedSetting;
}
$SettingsLookup{$SettingName} = {
%{ $SettingsLookup{$SettingName} },
%ModifiedSetting,
};
}
my @Settings = map { $SettingsLookup{$_} } ( sort keys %SettingsLookup );
return @Settings;
}
=head2 _HandleSettingsToDeploy()
Creates modified versions of dirty settings to deploy and removed the dirty flag.
NotDirty: Removes dirty flag just for default settings
AllSettings: Create a version for all dirty settings and removed dirty flags for all default and modified settings
DirtySettings: Create a version and remove dirty fag for the modified settings in the list, remove dirty flag for all default settings
my $Success = $SysConfigObject->_HandleSettingsToDeploy(
NotDirty => 1, # optional - exclusive (1||0)
AllSettings => 1, # optional - exclusive (1||0)
DirtySettings => [ 'SettingName1', 'SettingName2' ], # optional - exclusive
DeploymentTimeStamp => 2017-12-12 12:00:00'
UserID => 123,
);
Returns:
$Success = 1; # or false in case of a failure
=cut
sub _HandleSettingsToDeploy {
my ( $Self, %Param ) = @_;
for my $Needed (qw(UserID DeploymentTimeStamp)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed",
);
return;
}
}
if ( !$Param{NotDirty} && !$Param{DirtySettings} ) {
$Param{AllSettings} = 1;
}
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
# Remove is dirty flag for default settings.
my $DefaultCleanup = $SysConfigDBObject->DefaultSettingDirtyCleanUp(
AllSettings => $Param{AllSettings},
);
if ( !$DefaultCleanup ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not remove IsDirty flag from default settings",
);
}
return 1 if $Param{NotDirty};
my %DirtySettingsLookup = map { $_ => 1 } @{ $Param{DirtySettings} // [] };
# Get all dirty modified settings.
my @DirtyModifiedList = $SysConfigDBObject->ModifiedSettingListGet(
IsGlobal => 1,
IsDirty => 1,
);
my %VersionsAdded;
my @ModifiedDeleted;
my @ModifiedIDs;
my $Error;
# Create a new version for the modified settings.
SETTING:
for my $Setting (@DirtyModifiedList) {
# Skip setting if it is not in the list (and it is not a full deployment)
next SETTING if !$Param{AllSettings} && !$DirtySettingsLookup{ $Setting->{Name} };
my %DefaultSettingVersionGetLast = $SysConfigDBObject->DefaultSettingVersionGetLast(
DefaultID => $Setting->{DefaultID},
);
my $ModifiedVersionID = $SysConfigDBObject->ModifiedSettingVersionAdd(
%{$Setting},
DefaultVersionID => $DefaultSettingVersionGetLast{DefaultVersionID},
DeploymentTimeStamp => $Param{DeploymentTimeStamp},
UserID => $Param{UserID},
);
if ( !$ModifiedVersionID ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not create a modified setting version for $Setting->{Name}! Rolling back.",
);
$Error = 1;
last SETTING;
}
$VersionsAdded{ $Setting->{Name} } = $ModifiedVersionID;
if ( !$Setting->{ResetToDefault} ) {
push @ModifiedIDs, $Setting->{ModifiedID};
next SETTING;
}
# In case a setting value reset, delete the modified value.
my $ModifiedDelete = $SysConfigDBObject->ModifiedSettingDelete(
ModifiedID => $Setting->{ModifiedID},
);
if ( !$ModifiedDelete ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"Could not delete the modified setting for $Setting->{Name} on reset action! Rolling back.",
);
$Error = 1;
last SETTING;
}
push @ModifiedDeleted, $Setting;
}
# In case of an error:
# Remove "all" added versions for "all" settings for this deployment.
# Restore "all" deleted modified settings.
if ($Error) {
for my $SettingName ( sort keys %VersionsAdded ) {
my $Success = $SysConfigDBObject->ModifiedSettingVersionDelete(
ModifiedVersionID => $VersionsAdded{$SettingName},
);
}
for my $Setting (@ModifiedDeleted) {
my $Success = $SysConfigDBObject->ModifiedSettingAdd(
%{$Setting},
DeploymentExclusiveLockGUID => $Param{DeploymentExclusiveLockGUID},
UserID => $Setting->{ChangeBy},
);
}
return;
}
# Do not clean dirty flag if no setting version was created and it is not a full deployment
return 1 if !$Param{AllSettings} && !@ModifiedIDs;
my %Options;
if ( !$Param{AllSettings} ) {
$Options{ModifiedIDs} = \@ModifiedIDs;
}
# Remove is dirty flag for modified settings.
my $ModifiedCleanup = $SysConfigDBObject->ModifiedSettingDirtyCleanUp(%Options);
if ( !$ModifiedCleanup ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not remove IsDirty flag from modified settings",
);
}
return 1;
}
=head2 _SettingTranslatedGet()
Helper method for ConfigurationTranslatedGet().
my %Result = $SysConfigObject->_SettingTranslatedGet(
Language => 'de', # (required) User language
Name => 'SettingName', # (required) Setting name
Silent => 1, # (optional) Default 1
);
Returns:
%Result = (
'ACL::CacheTTL' => {
'Category' => 'OTRS',
'IsInvisible' => '0',
'Metadata' => "ACL::CacheTTL--- '3600'
Cache-Zeit in Sekunden f\x{fc}r Datenbank ACL-Backends.",
);
=cut
sub _SettingTranslatedGet {
my ( $Self, %Param ) = @_;
# Check needed stuff.
for my $Needed (qw(Language Name)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $CacheType = 'SysConfig';
my $CacheKey = "SettingTranslatedGet::$Param{Language}::$Param{Name}";
# Return cache.
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
return %{$Cache} if ref $Cache eq 'HASH';
my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML');
my %Categories = $Self->ConfigurationCategoriesGet();
my %SettingTranslated = $Self->SettingGet(
Name => $Param{Name},
Translate => 1,
);
my $Metadata = $Param{Name};
$Metadata .= $YAMLObject->Dump(
Data => $SettingTranslated{EffectiveValue},
);
$Metadata .= $SettingTranslated{Description};
my %Result;
$Result{ $Param{Name} }->{Metadata} = lc $Metadata;
# Check setting category.
my $SettingCategory;
my $Silent = $Param{Silent} // 1;
CATEGORY:
for my $Category ( sort keys %Categories ) {
if ( grep { $_ eq $SettingTranslated{XMLFilename} } @{ $Categories{$Category}->{Files} } ) {
$SettingCategory = $Category;
last CATEGORY;
}
}
if ( !$SettingCategory ) {
if ( !$Silent ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Category couldn't be determined for $Param{Name}!",
);
}
$SettingCategory = '-Unknown-';
}
$Result{ $Param{Name} }->{Category} = $SettingCategory;
$Result{ $Param{Name} }->{IsInvisible} = $SettingTranslated{IsInvisible};
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \%Result,
TTL => $Self->{CacheTTL} || 24 * 60 * 60,
);
return %Result;
}
=head2 _ValueTypesList()
Returns a hash of forbidden value types.
my @ValueTypes = $SysConfigObject->_ValueTypesList();
Returns:
@ValueTypes = (
"Checkbox",
"Select",
...
);
=cut
sub _ValueTypesList {
my ( $Self, %Param ) = @_;
my $CacheType = 'SysConfig';
my $CacheKey = '_ValueTypesList';
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# Return cache.
my $Cache = $CacheObject->Get(
Type => $CacheType,
Key => $CacheKey,
);
return @{$Cache} if ref $Cache eq 'ARRAY';
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my @Files = $MainObject->DirectoryRead(
Directory => $Self->{Home} . "/Kernel/System/SysConfig/ValueType",
Filter => '*.pm',
);
my @Result;
for my $File (@Files) {
my $ValueType = $File;
# Remove folder path.
$ValueType =~ s{^.*/}{}sm;
# Remove extension
$ValueType =~ s{\.pm$}{}sm;
push @Result, $ValueType;
}
$CacheObject->Set(
Type => $CacheType,
Key => $CacheKey,
Value => \@Result,
TTL => 24 * 3600, # 1 day
);
return @Result;
}
=head2 _DefaultSettingAddBulk()
Helper method for ConfigurationXML2DB() - bulk insert.
my $Success = $SysConfigObject->_DefaultSettingAddBulk(
Settings => { # (required) Hash of settings to insert
'SettingName' => {
},
...
},
SettingList => [ # (required) List of settings
...
],
UserID => 1, # (required) UserID
);
=cut
sub _DefaultSettingAddBulk {
my ( $Self, %Param ) = @_;
# Check needed stuff.
for my $Needed (qw(Settings SettingList UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# Check needed stuff.
if ( ref $Param{Settings} ne 'HASH' ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Settings must be a HASH ref!",
);
return;
}
my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML');
my %Settings = %{ $Param{Settings} };
my @SettingList = @{ $Param{SettingList} };
for my $SettingName ( sort keys %{ $Param{Settings} } ) {
# Create a local clone of the value to prevent any modification.
my $Value = $StorableObject->Clone(
Data => $Settings{$SettingName}->{XMLContentParsed}->{Value},
);
$Settings{$SettingName}->{EffectiveValue} = $Self->SettingEffectiveValueGet(
Value => $Value,
);
# Serialize values that doesn't have string representation.
$Settings{$SettingName}->{EffectiveValue} = $YAMLObject->Dump(
Data => $Settings{$SettingName}->{EffectiveValue},
);
$Settings{$SettingName}->{XMLContentParsedYAML} = $YAMLObject->Dump(
Data => $Settings{$SettingName}->{XMLContentParsed},
);
}
my $Success = $SysConfigDBObject->DefaultSettingBulkAdd(
Settings => \%Settings,
SettingList => \@SettingList,
UserID => $Param{UserID},
);
if ( !$Success ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "System was unable to rebuild config!"
);
return;
}
# Get again all settings.
@SettingList = $Self->ConfigurationList(
IncludeInvisible => 1,
);
$Success = $SysConfigDBObject->DefaultSettingVersionBulkAdd(
Settings => \%Settings,
SettingList => \@SettingList,
UserID => $Param{UserID},
);
if ( !$Success ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "System was unable to rebuild config!"
);
return;
}
return 1;
}
1;
=head1 TERMS AND CONDITIONS
This software is part of the OTRS project (L<https://otrs.org/>).
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 L<https://www.gnu.org/licenses/gpl-3.0.txt>.
=cut