# --
# 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 = '
' # 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' => '
',
);
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{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 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
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/\/$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 => '
Test.
# ...
',
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).
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.
=cut