1# --
2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
3# --
4# This software comes with ABSOLUTELY NO WARRANTY. For details, see
5# the enclosed file COPYING for license information (GPL). If you
6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
7# --
8## nofilter(TidyAll::Plugin::OTRS::Perl::LayoutObject)
9package Kernel::System::SysConfig;
10
11use strict;
12use warnings;
13
14use Time::HiRes();
15use utf8;
16
17use Kernel::System::VariableCheck qw(:all);
18use Kernel::Language qw(Translatable);
19use Kernel::Config;
20
21use parent qw(Kernel::System::AsynchronousExecutor);
22
23our @ObjectDependencies = (
24    'Kernel::Config',
25    'Kernel::Language',
26    'Kernel::Output::HTML::SysConfig',
27    'Kernel::System::Cache',
28    'Kernel::System::Log',
29    'Kernel::System::Main',
30    'Kernel::System::Package',
31    'Kernel::System::Storable',
32    'Kernel::System::SysConfig::DB',
33    'Kernel::System::SysConfig::Migration',
34    'Kernel::System::SysConfig::XML',
35    'Kernel::System::User',
36    'Kernel::System::YAML',
37);
38
39=head1 NAME
40
41Kernel::System::SysConfig - Functions to manage system configuration settings.
42
43=head1 DESCRIPTION
44
45All functions to manage system configuration settings.
46
47=head1 PUBLIC INTERFACE
48
49=head2 new()
50
51Don't use the constructor directly, use the ObjectManager instead:
52
53    my $SysConfigObject = $Kernel::OM->Get('Kernel::System::SysConfig');
54
55=cut
56
57## no critic (StringyEval)
58
59sub new {
60    my ( $Type, %Param ) = @_;
61
62    # allocate new hash for object
63    my $Self = {};
64    bless( $Self, $Type );
65
66    $Self->{ConfigObject} = $Kernel::OM->Get('Kernel::Config');
67
68    # get home directory
69    $Self->{Home} = $Self->{ConfigObject}->Get('Home');
70
71    # set utf-8 if used
72    $Self->{utf8}     = 1;
73    $Self->{FileMode} = ':utf8';
74
75    $Self->{ConfigDefaultObject} = Kernel::Config->new( Level => 'Default' );
76    $Self->{ConfigObject}        = Kernel::Config->new( Level => 'First' );
77    $Self->{ConfigClearObject}   = Kernel::Config->new( Level => 'Clear' );
78
79    # Load base files.
80    my $BaseDir = $Self->{Home} . '/Kernel/System/SysConfig/Base/';
81
82    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
83
84    FILENAME:
85    for my $Filename (qw(Framework.pm OTRSBusiness.pm)) {
86        my $BaseFile = $BaseDir . $Filename;
87        next FILENAME if !-e $BaseFile;
88
89        $BaseFile =~ s{\A.*\/(.+?).pm\z}{$1}xms;
90        my $BaseClassName = "Kernel::System::SysConfig::Base::$BaseFile";
91        if ( !$MainObject->RequireBaseClass($BaseClassName) ) {
92            $Self->FatalDie(
93                Message => "Could not load class $BaseClassName.",
94            );
95        }
96
97    }
98
99    return $Self;
100}
101
102=head2 SettingGet()
103
104Get SysConfig setting attributes.
105
106    my %Setting = $SysConfigObject->SettingGet(
107        Name            => 'Setting::Name',  # (required) Setting name
108        Default         => 1,                # (optional) Returns the default setting attributes only
109        ModifiedID      => '123',            # (optional) Get setting value for given ModifiedID.
110        TargetUserID    => 1,                # (optional) Get setting value for specific user.
111        Deployed        => 1,                # (optional) Get deployed setting value. Default 0.
112        OverriddenInXML => 1,                # (optional) Consider changes made in perl files. Default 0.
113        Translate       => 1,                # (optional) Translate translatable strings in EffectiveValue. Default 0.
114        NoLog           => 1,                # (optional) Do not log error if a setting does not exist.
115        NoCache         => 1,                # (optional) Do not create cache.
116        UserID          => 1,                # Required only if OverriddenInXML is set.
117    );
118
119Returns:
120
121    %Setting = (
122        DefaultID                => 123,
123        ModifiedID               => 456,         # optional
124        Name                     => "ProductName",
125        Description              => "Defines the name of the application ...",
126        Navigation               => "ASimple::Path::Structure",
127        IsInvisible              => 1,           # 1 or 0
128        IsReadonly               => 0,           # 1 or 0
129        IsRequired               => 1,           # 1 or 0
130        IsModified               => 1,           # 1 or 0
131        IsValid                  => 1,           # 1 or 0
132        HasConfigLevel           => 200,
133        UserModificationPossible => 0,           # 1 or 0
134        UserModificationActive   => 0,           # 1 or 0
135        UserPreferencesGroup     => 'Advanced',  # optional
136        XMLContentRaw            => "The XML structure as it is on the config file",
137        XMLContentParsed         => "XML parsed to Perl",
138        XMLFilename              => "Framework.xml",
139        EffectiveValue           => "Product 6",
140        IsDirty                  => 1,           # 1 or 0
141        ExclusiveLockGUID        => 'A32CHARACTERLONGSTRINGFORLOCKING',
142        ExclusiveLockUserID      => 1,
143        ExclusiveLockExpiryTime  => '2016-05-29 11:09:04',
144        CreateTime               => "2016-05-29 11:04:04",
145        CreateBy                 => 1,
146        ChangeTime               => "2016-05-29 11:04:04",
147        ChangeBy                 => 1,
148        DefaultValue             => 'Old default value',
149        OverriddenFileName        => '/opt/otrs/Kernel/Config/Files/ZZZ.pm',
150    );
151
152=cut
153
154sub SettingGet {
155    my ( $Self, %Param ) = @_;
156
157    # Check needed stuff.
158    if ( !$Param{Name} ) {
159        $Kernel::OM->Get('Kernel::System::Log')->Log(
160            Priority => 'error',
161            Message  => 'Need Name!',
162        );
163        return;
164    }
165
166    if ( $Param{OverriddenInXML} && !$Param{UserID} ) {
167        $Kernel::OM->Get('Kernel::System::Log')->Log(
168            Priority => 'error',
169            Message  => 'UserID is needed when OverriddenInXML is set!',
170        );
171        return;
172    }
173
174    $Param{Translate} //= 0;    # don't translate by default
175
176    my $ConfigObject      = $Kernel::OM->Get('Kernel::Config');
177    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
178
179    # Get default setting.
180    my %Setting = $SysConfigDBObject->DefaultSettingGet(
181        Name    => $Param{Name},
182        NoCache => $Param{NoCache},
183    );
184
185    # setting was not found
186    if ( !%Setting ) {
187
188        # do not log an error if parameter NoLog is true
189        if ( !$Param{NoLog} ) {
190            $Kernel::OM->Get('Kernel::System::Log')->Log(
191                Priority => 'error',
192                Message  => "Setting $Param{Name} is invalid!",
193            );
194        }
195
196        return;
197    }
198
199    $Setting{DefaultValue} = $Setting{EffectiveValue};
200
201    # Return default setting if specified (otherwise continue with modified setting).
202    if ( $Param{Default} ) {
203        return %Setting;
204    }
205
206    # Check if modified setting available
207    my %ModifiedSetting;
208    if ( $Param{ModifiedID} ) {
209
210        # Get settings with given ModifiedID.
211        %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
212            ModifiedID => $Param{ModifiedID},
213            IsGlobal   => 1,
214            NoCache    => $Param{NoCache},
215        );
216
217        # prevent using both parameters.
218        $Param{Deployed}     = undef;
219        $Param{TargetUserID} = undef;
220    }
221    else {
222
223        # Get latest modified setting.
224        %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
225            Name     => $Param{Name},
226            IsGlobal => 1,
227            NoCache  => $Param{NoCache},
228        );
229    }
230
231    if ( $Param{TargetUserID} ) {
232
233        if ( IsHashRefWithData( \%ModifiedSetting ) ) {
234
235            # There is modified setting, but we need last deployed version.
236            %ModifiedSetting = $SysConfigDBObject->ModifiedSettingVersionGetLast(
237                Name => $ModifiedSetting{Name},
238            );
239
240            # Use global (deployed) modified settings as "default" (if any)
241            if ( IsHashRefWithData( \%ModifiedSetting ) ) {
242                %Setting = (
243                    %Setting,
244                    %ModifiedSetting,
245                );
246            }
247        }
248
249        # get user specific settings
250        %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
251            Name         => $Param{Name},
252            TargetUserID => $Param{TargetUserID},
253        );
254
255        # prevent using both parameters.
256        $Param{Deployed} = undef;
257    }
258
259    if ( $Param{Deployed} ) {
260
261        # get the previous deployed state of this setting
262        my %SettingDeployed = $SysConfigDBObject->ModifiedSettingVersionGetLast(
263            Name => $Setting{Name},
264        );
265
266        if ( !IsHashRefWithData( \%SettingDeployed ) ) {
267
268            # if this setting was never deployed before, get the default state
269
270            # Get default version.
271            %SettingDeployed = $SysConfigDBObject->DefaultSettingGet(
272                DefaultID => $Setting{DefaultID},
273                NoCache   => $Param{NoCache},
274            );
275        }
276
277        if ( IsHashRefWithData( \%SettingDeployed ) ) {
278            %Setting = (
279                %Setting,
280                %SettingDeployed
281            );
282        }
283    }
284
285    # default
286    $Setting{IsModified} = 0;
287
288    if ( IsHashRefWithData( \%ModifiedSetting ) ) {
289
290        my $IsModified = DataIsDifferent(
291            Data1 => \$Setting{EffectiveValue},
292            Data2 => \$ModifiedSetting{EffectiveValue},
293        ) || 0;
294
295        $IsModified ||= $ModifiedSetting{IsValid} != $Setting{IsValid};
296        $IsModified ||= $ModifiedSetting{UserModificationActive} != $Setting{UserModificationActive};
297
298        $Setting{IsModified} = $IsModified ? 1 : 0;
299
300        if ( !$Param{Deployed} ) {
301
302            # Update setting attributes.
303            ATTRIBUTE:
304            for my $Attribute (
305                qw(ModifiedID IsValid UserModificationActive EffectiveValue IsDirty
306                CreateTime CreateBy ChangeTime ChangeBy SettingUID
307                )
308                )
309            {
310                next ATTRIBUTE if !defined $ModifiedSetting{$Attribute};
311
312                $Setting{$Attribute} = $ModifiedSetting{$Attribute};
313            }
314        }
315    }
316
317    if ( $Param{OverriddenInXML} ) {
318
319        # get the previous deployed state of this setting
320        my %SettingDeployed = $SysConfigDBObject->ModifiedSettingVersionGetLast(
321            Name => $Setting{Name},
322        );
323
324        if ( !IsHashRefWithData( \%SettingDeployed ) ) {
325
326            # if this setting was never deployed before, get the default state
327
328            # Get default version.
329            %SettingDeployed = $SysConfigDBObject->DefaultSettingGet(
330                DefaultID => $Setting{DefaultID},
331                NoCache   => $Param{NoCache},
332            );
333        }
334
335        # Get real EffectiveValue - EffectiveValue from DB could be modified in the ZZZAbc.pm file.
336        my $LoadedEffectiveValue = $Self->GlobalEffectiveValueGet(
337            SettingName => $Setting{Name},
338        );
339
340        my $IsOverridden = DataIsDifferent(
341            Data1 => $SettingDeployed{EffectiveValue} // {},
342            Data2 => $LoadedEffectiveValue            // {},
343        );
344
345        if ($IsOverridden) {
346            $Setting{OverriddenFileName} = $Self->OverriddenFileNameGet(
347                SettingName    => $Setting{Name},
348                EffectiveValue => $Setting{EffectiveValue},
349                UserID         => $Param{UserID},
350            );
351
352            # Update EffectiveValue.
353            if ( $Setting{OverriddenFileName} ) {
354                $Setting{EffectiveValue} = $LoadedEffectiveValue;
355            }
356        }
357    }
358
359    if ( $Param{Translate} ) {
360
361        if (%ModifiedSetting) {
362            $Setting{XMLContentParsed}->{Value} = $Self->SettingModifiedXMLContentParsedGet(
363                ModifiedSetting => {
364                    EffectiveValue => $Setting{EffectiveValue},
365                },
366                DefaultSetting => {
367                    XMLContentParsed => $Setting{XMLContentParsed},
368                },
369            );
370        }
371
372        # Update EffectiveValue with translated strings
373        $Setting{EffectiveValue} = $Self->SettingEffectiveValueGet(
374            Value     => $Setting{XMLContentParsed}->{Value},
375            Translate => 1,
376        );
377
378        $Setting{Description} = $Kernel::OM->Get('Kernel::Language')->Translate(
379            $Setting{Description},
380        );
381    }
382
383    # If setting is overridden in the perl file, using the "delete" statement, EffectiveValue is undef.
384    $Setting{EffectiveValue} //= '';
385
386    # Return updated default.
387    return %Setting;
388}
389
390=head2 SettingUpdate()
391
392Update an existing SysConfig Setting.
393
394    my %Result = $SysConfigObject->SettingUpdate(
395        Name                   => 'Setting::Name',           # (required) setting name
396        IsValid                => 1,                         # (optional) 1 or 0, modified 0
397        EffectiveValue         => $SettingEffectiveValue,    # (optional)
398        UserModificationActive => 0,                         # (optional) 1 or 0, modified 0
399        TargetUserID           => 2,                         # (optional) ID of the user for which the modified setting is meant,
400                                                             #   leave it undef for global changes.
401        ExclusiveLockGUID      => $LockingString,            # the GUID used to locking the setting
402        UserID                 => 1,                         # (required) UserID
403        NoValidation           => 1,                         # (optional) no value type validation.
404    );
405
406Returns:
407
408    %Result = (
409        Success => 1,        # or false in case of an error
410        Error   => undef,    # error message
411    );
412
413=cut
414
415sub SettingUpdate {
416    my ( $Self, %Param ) = @_;
417
418    for my $Needed (qw(Name UserID)) {
419        if ( !$Param{$Needed} ) {
420            $Kernel::OM->Get('Kernel::System::Log')->Log(
421                Priority => 'error',
422                Message  => "Need $Needed!",
423            );
424
425            return;
426        }
427    }
428
429    if ( !$Param{TargetUserID} && !$Param{ExclusiveLockGUID} ) {
430        $Kernel::OM->Get('Kernel::System::Log')->Log(
431            Priority => 'error',
432            Message  => "Need TargetUserID or ExclusiveLockGUID!",
433        );
434    }
435
436    my %Result = (
437        Success => 1,
438    );
439
440    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
441
442    # Get default setting
443    my %Setting = $SysConfigDBObject->DefaultSettingGet(
444        Name => $Param{Name},
445    );
446
447    # Make sure that required settings can't be disabled.
448    if ( $Setting{IsRequired} ) {
449        $Param{IsValid} = 1;
450    }
451
452    # Return if setting does not exists.
453    if ( !%Setting ) {
454        $Kernel::OM->Get('Kernel::System::Log')->Log(
455            Priority => 'error',
456            Message  => "Setting $Param{Name} does not exists!",
457        );
458
459        %Result = (
460            Success => 0,
461            Error   => $Kernel::OM->Get('Kernel::Language')->Translate(
462                "Setting %s does not exists!",
463                $Param{Name},
464            ),
465        );
466        return %Result;
467    }
468
469    # Default should be locked (for global updates).
470    my $LockedByUser;
471    if ( !$Param{TargetUserID} ) {
472        $LockedByUser = $SysConfigDBObject->DefaultSettingIsLockedByUser(
473            DefaultID           => $Setting{DefaultID},
474            ExclusiveLockUserID => $Param{UserID},
475            ExclusiveLockGUID   => $Param{ExclusiveLockGUID},
476        );
477
478        if ( !$LockedByUser ) {
479            $Kernel::OM->Get('Kernel::System::Log')->Log(
480                Priority => 'error',
481                Message  => "Setting $Param{Name} is not locked to this user!",
482            );
483
484            %Result = (
485                Success => 0,
486                Error   => $Kernel::OM->Get('Kernel::Language')->Translate(
487                    "Setting %s is not locked to this user!",
488                    $Param{Name},
489                ),
490            );
491            return %Result;
492        }
493    }
494
495    # Do not perform EffectiveValueCheck if user wants to disable the setting.
496    if ( $Param{IsValid} ) {
497
498        # Effective value must match in structure to the default and individual values should be
499        #   valid according to its value types.
500        my %EffectiveValueCheck = $Self->SettingEffectiveValueCheck(
501            XMLContentParsed => $Setting{XMLContentParsed},
502            EffectiveValue   => $Param{EffectiveValue},
503            NoValidation     => $Param{NoValidation} //= 0,
504            UserID           => $Param{UserID},
505        );
506
507        if ( !$EffectiveValueCheck{Success} ) {
508            my $Error = $EffectiveValueCheck{Error} || 'Unknown error!';
509
510            $Kernel::OM->Get('Kernel::System::Log')->Log(
511                Priority => 'error',
512                Message  => "EffectiveValue is invalid! $Error",
513            );
514
515            %Result = (
516                Success => 0,
517                Error   => $Kernel::OM->Get('Kernel::Language')->Translate(
518                    "Setting value is not valid!",
519                ),
520            );
521            return %Result;
522        }
523    }
524
525    # Get modified setting (if any).
526    my %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
527        Name     => $Param{Name},
528        IsGlobal => 1,
529    );
530
531    if ( !defined $Param{EffectiveValue} ) {
532
533        # In the case that we want only to enable/disable setting,
534        #    old effective value will be preserved.
535        $Param{EffectiveValue} = $ModifiedSetting{EffectiveValue} // $Setting{EffectiveValue};
536    }
537
538    my $UserModificationActive = $Param{UserModificationActive} //= $Setting{UserModificationActive};
539
540    if ( $Param{TargetUserID} ) {
541        if ( IsHashRefWithData( \%ModifiedSetting ) ) {
542
543            # override default setting with global modified setting
544            %Setting = (
545                %Setting,
546                %ModifiedSetting,
547            );
548        }
549
550        %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
551            Name         => $Param{Name},
552            TargetUserID => $Param{TargetUserID},
553        );
554
555        $UserModificationActive = undef;    # prevent setting this value
556
557        my %GlobalSetting = $Self->SettingGet(
558            Name            => $Param{Name},
559            OverriddenInXML => 1,
560            UserID          => 1,
561        );
562
563        $Setting{EffectiveValue} = $GlobalSetting{EffectiveValue};
564    }
565
566    # Add new modified setting (if there wasn't).
567    if ( !%ModifiedSetting ) {
568
569        # Check if provided EffectiveValue is same as in Default
570        my $IsDifferent = DataIsDifferent(
571            Data1 => \$Setting{EffectiveValue},
572            Data2 => \$Param{EffectiveValue},
573        ) || 0;
574
575        if ( defined $Param{IsValid} ) {
576            $IsDifferent ||= $Setting{IsValid} != $Param{IsValid};
577        }
578
579        $IsDifferent ||= $Setting{UserModificationActive} != $Param{UserModificationActive};
580
581        if ($IsDifferent) {
582
583            my $ModifiedID = $SysConfigDBObject->ModifiedSettingAdd(
584                DefaultID              => $Setting{DefaultID},
585                Name                   => $Setting{Name},
586                IsValid                => $Param{IsValid} //= $Setting{IsValid},
587                EffectiveValue         => $Param{EffectiveValue},
588                UserModificationActive => $UserModificationActive,
589                TargetUserID           => $Param{TargetUserID},
590                ExclusiveLockGUID      => $Param{ExclusiveLockGUID},
591                UserID                 => $Param{UserID},
592            );
593            if ( !$ModifiedID ) {
594                $Kernel::OM->Get('Kernel::System::Log')->Log(
595                    Priority => 'error',
596                    Message  => "Could not add modified setting!",
597                );
598                %Result = (
599                    Success => 0,
600                    Error   => $Kernel::OM->Get('Kernel::Language')->Translate(
601                        "Could not add modified setting!",
602                    ),
603                );
604                return %Result;
605            }
606        }
607    }
608    else {
609
610        # Check if provided EffectiveValue is same as in last modified EffectiveValue
611        my $IsDifferent = DataIsDifferent(
612            Data1 => \$ModifiedSetting{EffectiveValue},
613            Data2 => \$Param{EffectiveValue},
614        ) || 0;
615
616        if ( defined $Param{IsValid} ) {
617            $IsDifferent ||= $ModifiedSetting{IsValid} != $Param{IsValid};
618        }
619
620        $IsDifferent ||= $ModifiedSetting{UserModificationActive} != $Param{UserModificationActive};
621
622        if ($IsDifferent) {
623
624            my %ModifiedSettingVersion = $SysConfigDBObject->ModifiedSettingVersionGetLast(
625                Name => $ModifiedSetting{Name},
626            );
627
628            my $EffectiveValueModifiedSinceDeployment = 1;
629            if ( $ModifiedSettingVersion{ModifiedVersionID} ) {
630
631                my %ModifiedSettingLastDeployed = $SysConfigDBObject->ModifiedSettingVersionGet(
632                    ModifiedVersionID => $ModifiedSettingVersion{ModifiedVersionID},
633                );
634
635                $EffectiveValueModifiedSinceDeployment = DataIsDifferent(
636                    Data1 => \$ModifiedSettingLastDeployed{EffectiveValue},
637                    Data2 => \$Param{EffectiveValue},
638                ) || 0;
639
640                if ( defined $Param{IsValid} ) {
641                    $EffectiveValueModifiedSinceDeployment ||= $ModifiedSettingLastDeployed{IsValid} != $Param{IsValid};
642                }
643
644                $EffectiveValueModifiedSinceDeployment
645                    ||= $ModifiedSettingLastDeployed{UserModificationActive} != $Param{UserModificationActive};
646
647            }
648            elsif ( !IsHashRefWithData( \%ModifiedSettingVersion ) ) {
649                $EffectiveValueModifiedSinceDeployment = DataIsDifferent(
650                    Data1 => \$Setting{EffectiveValue},
651                    Data2 => \$Param{EffectiveValue},
652                ) || 0;
653
654                if ( defined $Param{IsValid} ) {
655                    $EffectiveValueModifiedSinceDeployment ||= $Setting{IsValid} != $Param{IsValid};
656                }
657
658                $EffectiveValueModifiedSinceDeployment
659                    ||= $Setting{UserModificationActive} != $Param{UserModificationActive};
660            }
661
662            # Update the existing modified setting.
663            my $Success = $SysConfigDBObject->ModifiedSettingUpdate(
664                ModifiedID             => $ModifiedSetting{ModifiedID},
665                DefaultID              => $Setting{DefaultID},
666                Name                   => $Setting{Name},
667                IsValid                => $Param{IsValid} //= $ModifiedSetting{IsValid},
668                EffectiveValue         => $Param{EffectiveValue},
669                UserModificationActive => $UserModificationActive,
670                TargetUserID           => $Param{TargetUserID} //= $ModifiedSetting{TargetUserID},
671                ExclusiveLockGUID      => $Param{ExclusiveLockGUID},
672                UserID                 => $Param{UserID},
673                IsDirty                => $EffectiveValueModifiedSinceDeployment ? 1 : 0,
674            );
675            if ( !$Success ) {
676                $Kernel::OM->Get('Kernel::System::Log')->Log(
677                    Priority => 'error',
678                    Message  => "Could not update modified setting!",
679                );
680                %Result = (
681                    Success => 0,
682                    Error   => $Kernel::OM->Get('Kernel::Language')->Translate(
683                        "Could not update modified setting!",
684                    ),
685                );
686                return %Result;
687            }
688        }
689    }
690
691    # When a setting is set to invalid all modified settings for users has to be removed.
692    if (
693        !$Param{IsValid}
694        && !$Param{TargetUserID}
695        && $Self->can('UserSettingValueDelete')    # OTRS Business Solution™
696        )
697    {
698        $Self->UserSettingValueDelete(
699            Name       => $Setting{Name},
700            ModifiedID => 'All',
701            UserID     => $Param{UserID},
702        );
703    }
704
705    if ( !$Param{TargetUserID} ) {
706
707        # Unlock setting so it can be locked again afterwards.
708        my $Success = $SysConfigDBObject->DefaultSettingUnlock(
709            DefaultID => $Setting{DefaultID},
710        );
711        if ( !$Success ) {
712            $Kernel::OM->Get('Kernel::System::Log')->Log(
713                Priority => 'error',
714                Message  => "Setting could not be unlocked!",
715            );
716            %Result = (
717                Success => 0,
718                Error   => $Kernel::OM->Get('Kernel::Language')->Translate(
719                    "Setting could not be unlocked!",
720                ),
721            );
722            return %Result;
723        }
724    }
725
726    return %Result;
727}
728
729=head2 SettingLock()
730
731Lock setting(s) to the particular user.
732
733    my $ExclusiveLockGUID = $SysConfigObject->SettingLock(
734        DefaultID => 1,                     # the ID of the setting that needs to be locked
735                                            #    or
736        Name      => 'SettingName',         # the Name of the setting that needs to be locked
737                                            #    or
738        LockAll   => 1,                     # system locks all settings
739        Force     => 1,                     # (optional) Force locking (do not check if it's already locked by another user). Default: 0.
740        UserID    => 1,                     # (required)
741    );
742
743Returns:
744
745    $ExclusiveLockGUID = 'azzHab72wIlAXDrxHexsI5aENsESxAO7';     # Setting locked
746
747    or
748
749    $ExclusiveLockGUID = undef;     # Not locked
750
751=cut
752
753sub SettingLock {
754    my ( $Self, %Param ) = @_;
755
756    return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingLock(%Param);
757}
758
759=head2 SettingUnlock()
760
761Unlock particular or all Setting(s).
762
763    my $Success = $SysConfigObject->SettingUnlock(
764        DefaultID => 1,                     # the ID of the setting that needs to be unlocked
765                                            #   or
766        Name      => 'SettingName',         # the name of the setting that needs to be locked
767                                            #   or
768        UnlockAll => 1,                     # unlock all settings
769    );
770
771Returns:
772
773    $Success = 1;
774
775=cut
776
777sub SettingUnlock {
778    my ( $Self, %Param ) = @_;
779
780    return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingUnlock(%Param);
781}
782
783=head2 SettingLockCheck()
784
785Check setting lock status.
786
787    my %Result = $SysConfigObject->SettingLockCheck(
788        DefaultID           => 1,                     # the ID of the setting that needs to be checked
789        ExclusiveLockGUID   => 1,                     # lock GUID
790        ExclusiveLockUserID => 1,                     # UserID
791    );
792
793Returns:
794
795    %Result = (
796        Locked => 1,                        # lock status;
797                                            # 0 - unlocked
798                                            # 1 - locked to another user
799                                            # 2 - locked to provided user
800        User   => {                         # User data, provided only if Locked = 1
801            UserLogin => ...,
802            UserFirstname => ...,
803            UserLastname => ...,
804            ...
805        },
806    );
807
808=cut
809
810sub SettingLockCheck {
811    my ( $Self, %Param ) = @_;
812
813    for my $Needed (qw(DefaultID ExclusiveLockGUID ExclusiveLockUserID)) {
814        if ( !$Param{$Needed} ) {
815            $Kernel::OM->Get('Kernel::System::Log')->Log(
816                Priority => 'error',
817                Message  => "Need $Needed!",
818            );
819            return;
820        }
821    }
822
823    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
824
825    my %Result = (
826        Locked => 0,
827    );
828
829    my $LockedByUser = $SysConfigDBObject->DefaultSettingIsLockedByUser(
830        DefaultID           => $Param{DefaultID},
831        ExclusiveLockUserID => $Param{ExclusiveLockUserID},
832        ExclusiveLockGUID   => $Param{ExclusiveLockGUID},
833    );
834
835    if ($LockedByUser) {
836
837        # setting locked to the provided user
838        $Result{Locked} = 2;
839    }
840    else {
841        # check if setting is locked to another user
842        my $UserID = $SysConfigDBObject->DefaultSettingIsLocked(
843            DefaultID     => $Param{DefaultID},
844            GetLockUserID => 1,
845        );
846
847        if ($UserID) {
848
849            # get user data
850            $Result{Locked} = 1;
851
852            my %User = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
853                UserID => $UserID,
854            );
855
856            $Result{User} = \%User;
857        }
858    }
859
860    return %Result;
861}
862
863=head2 SettingEffectiveValueGet()
864
865Calculate effective value for a given parsed XML structure.
866
867    my $Result = $SysConfigObject->SettingEffectiveValueGet(
868        Translate => 1,                      # (optional) Translate translatable strings. Default 0.
869        Value  => [                          # (required) parsed XML structure
870            {
871                'Item' => [
872                    {
873                        'ValueType' => 'String',
874                        'Content' => '3600',
875                        'ValueRegex' => ''
876                    },
877                ],
878            },
879            Objects => {
880                Select => { ... },
881                PerlModule => { ... },
882                # ...
883            }
884        ];
885    );
886
887Returns:
888
889    $Result = '3600';
890
891=cut
892
893sub SettingEffectiveValueGet {
894    my ( $Self, %Param ) = @_;
895
896    for my $Needed (qw(Value)) {
897        if ( !$Param{$Needed} ) {
898            $Kernel::OM->Get('Kernel::System::Log')->Log(
899                Priority => 'error',
900                Message  => "Need $Needed!",
901            );
902            return;
903        }
904    }
905
906    my %ForbiddenValueTypes = %{ $Self->{ForbiddenValueTypes} || {} };
907
908    if ( !%ForbiddenValueTypes ) {
909        %ForbiddenValueTypes = $Self->ForbiddenValueTypesGet();
910        $Self->{ForbiddenValueTypes} = \%ForbiddenValueTypes;
911    }
912
913    $Param{Translate} //= 0;
914
915    my $Result;
916
917    my %Objects;
918    if ( $Param{Objects} ) {
919        %Objects = %{ $Param{Objects} };
920    }
921
922    # Make sure structure is correct.
923    return $Result if !IsArrayRefWithData( $Param{Value} );
924    return $Result if !IsHashRefWithData( $Param{Value}->[0] );
925
926    my %Attributes;
927
928    if ( $Param{Value}->[0]->{Item} ) {
929
930        # Make sure structure is correct.
931        return $Result if !IsArrayRefWithData( $Param{Value}->[0]->{Item} );
932        return $Result if !IsHashRefWithData( $Param{Value}->[0]->{Item}->[0] );
933
934        # Set default ValueType.
935        my $ValueType = "String";
936
937        if ( $Param{Value}->[0]->{Item}->[0]->{ValueType} ) {
938            $ValueType = $Param{Value}->[0]->{Item}->[0]->{ValueType};
939        }
940
941        if ( !$Objects{$ValueType} ) {
942
943            # Make sure value type backend is available and syntactically correct.
944            my $Loaded = $Kernel::OM->Get('Kernel::System::Main')->Require(
945                "Kernel::System::SysConfig::ValueType::$ValueType",
946            );
947
948            return $Result if !$Loaded;
949
950            $Objects{$ValueType} = $Kernel::OM->Get(
951                "Kernel::System::SysConfig::ValueType::$ValueType",
952            );
953        }
954
955        # Create a local clone of the value to prevent any modification.
956        my $Value = $Kernel::OM->Get('Kernel::System::Storable')->Clone(
957            Data => $Param{Value}->[0]->{Item},
958        );
959
960        $Result = $Objects{$ValueType}->EffectiveValueGet(
961            Value     => $Value,
962            Translate => $Param{Translate},
963        );
964    }
965    elsif ( $Param{Value}->[0]->{Hash} ) {
966
967        # Make sure structure is correct.
968        return {} if !IsArrayRefWithData( $Param{Value}->[0]->{Hash} );
969        return {} if !IsHashRefWithData( $Param{Value}->[0]->{Hash}->[0] );
970        return {} if !IsArrayRefWithData( $Param{Value}->[0]->{Hash}->[0]->{Item} );
971
972        # Check for additional attributes in the DefaultItem.
973        if (
974            $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}
975            && ref $Param{Value}->[0]->{Hash}->[0]->{DefaultItem} eq 'ARRAY'
976            && scalar $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}
977            && ref $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0] eq 'HASH'
978            )
979        {
980            %Attributes = ();
981
982            my @ValueAttributeList = $Self->ValueAttributeList();
983
984            ATTRIBUTE:
985            for my $Attribute ( sort keys %{ $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0] } ) {
986                if (
987                    ( grep { $_ eq $Attribute } qw (Array Hash) )
988                    && $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$Attribute}->[0]->{DefaultItem}
989                    )
990                {
991                    $Attributes{DefaultItem}
992                        = $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$Attribute}->[0]->{DefaultItem};
993                }
994                next ATTRIBUTE if grep { $Attribute eq $_ } ( qw (Array Hash), @ValueAttributeList );
995
996                if (
997                    $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{Item}
998                    && $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{ValueType}
999                    )
1000                {
1001                    my $DefaultItemValueType = $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{ValueType};
1002                    if ( $ForbiddenValueTypes{$DefaultItemValueType} ) {
1003                        my $SubValueType
1004                            = $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{Item}->[0]->{ValueType};
1005
1006                        if ( !grep { $_ eq $SubValueType } @{ $ForbiddenValueTypes{$DefaultItemValueType} } ) {
1007                            next ATTRIBUTE;
1008                        }
1009                    }
1010                }
1011
1012                $Attributes{$Attribute} = $Param{Value}->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$Attribute};
1013            }
1014        }
1015
1016        ITEM:
1017        for my $Item ( @{ $Param{Value}->[0]->{Hash}->[0]->{Item} } ) {
1018
1019            next ITEM if !IsHashRefWithData($Item);
1020            next ITEM if !defined $Item->{Key};
1021
1022            if ( $Item->{Hash} || $Item->{Array} ) {
1023
1024                my $ItemKey = $Item->{Hash} ? 'Hash' : 'Array';
1025
1026                ATTRIBUTE:
1027                for my $Attribute ( sort keys %Attributes ) {
1028                    next ATTRIBUTE if defined $Item->{$Attribute};    # skip redefined values
1029
1030                    $Item->{$ItemKey}->[0]->{$Attribute} = $Attributes{$Attribute};
1031                }
1032
1033                my $Value = $Self->SettingEffectiveValueGet(
1034                    Value     => [$Item],
1035                    Objects   => \%Objects,
1036                    Translate => $Param{Translate},
1037                );
1038
1039                $Result->{ $Item->{Key} } = $Value;
1040            }
1041            elsif ( $Attributes{ValueType} || $Item->{ValueType} ) {
1042
1043                # Create a local clone of the item to prevent any modification.
1044                my $Clone = $Kernel::OM->Get('Kernel::System::Storable')->Clone(
1045                    Data => $Item,
1046                );
1047
1048                ATTRIBUTE:
1049                for my $Attribute ( sort keys %Attributes ) {
1050                    next ATTRIBUTE if defined $Clone->{$Attribute};    # skip redefined values
1051
1052                    $Clone->{$Attribute} = $Attributes{$Attribute};
1053                }
1054
1055                my $Value = $Self->SettingEffectiveValueGet(
1056                    Value => [
1057                        {
1058                            Item => [$Clone],
1059                        },
1060                    ],
1061                    Objects   => \%Objects,
1062                    Translate => $Param{Translate},
1063                );
1064
1065                $Result->{ $Item->{Key} } = $Value;
1066            }
1067            else {
1068
1069                $Item->{Content} //= '';
1070
1071                # Remove empty space at start and the end (with new lines).
1072                $Item->{Content} =~ s{^\n\s*(.*?)\n\s*$}{$1}gsmx;
1073
1074                $Result->{ $Item->{Key} } = $Item->{Content};
1075            }
1076        }
1077    }
1078    elsif ( $Param{Value}->[0]->{Array} ) {
1079
1080        # Make sure structure is correct
1081        return [] if !IsArrayRefWithData( $Param{Value}->[0]->{Array} );
1082        return [] if !IsHashRefWithData( $Param{Value}->[0]->{Array}->[0] );
1083        return [] if !IsArrayRefWithData( $Param{Value}->[0]->{Array}->[0]->{Item} );
1084
1085        # Check for additional attributes in the DefaultItem.
1086        if (
1087            $Param{Value}->[0]->{Array}->[0]->{DefaultItem}
1088            && ref $Param{Value}->[0]->{Array}->[0]->{DefaultItem} eq 'ARRAY'
1089            && scalar $Param{Value}->[0]->{Array}->[0]->{DefaultItem}
1090            && ref $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0] eq 'HASH'
1091            )
1092        {
1093            %Attributes = ();
1094
1095            ATTRIBUTE:
1096            for my $Attribute ( sort keys %{ $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0] } ) {
1097                if (
1098                    ( grep { $_ eq $Attribute } qw (Array Hash) )
1099                    && $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0]->{$Attribute}->[0]->{DefaultItem}
1100                    )
1101                {
1102                    $Attributes{DefaultItem}
1103                        = $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0]->{$Attribute}->[0]->{DefaultItem};
1104                }
1105                next ATTRIBUTE if grep { $Attribute eq $_ } qw (Array Hash Content SelectedID);
1106
1107                $Attributes{$Attribute} = $Param{Value}->[0]->{Array}->[0]->{DefaultItem}->[0]->{$Attribute};
1108            }
1109        }
1110
1111        my @Items;
1112
1113        ITEM:
1114        for my $Item ( @{ $Param{Value}->[0]->{Array}->[0]->{Item} } ) {
1115            next ITEM if !IsHashRefWithData($Item);
1116
1117            if ( $Item->{Hash} || $Item->{Array} ) {
1118                my $ItemKey = $Item->{Hash} ? 'Hash' : 'Array';
1119
1120                ATTRIBUTE:
1121                for my $Attribute ( sort keys %Attributes ) {
1122                    next ATTRIBUTE if defined $Item->{$Attribute};    # skip redefined values
1123
1124                    $Item->{$ItemKey}->[0]->{$Attribute} = $Attributes{$Attribute};
1125                }
1126
1127                my $Value = $Self->SettingEffectiveValueGet(
1128                    Value     => [$Item],
1129                    Objects   => \%Objects,
1130                    Translate => $Param{Translate},
1131                );
1132
1133                push @Items, $Value;
1134            }
1135            elsif ( $Attributes{ValueType} ) {
1136
1137                # Create a local clone of the item to prevent any modification.
1138                my $Clone = $Kernel::OM->Get('Kernel::System::Storable')->Clone(
1139                    Data => $Item,
1140                );
1141
1142                ATTRIBUTE:
1143                for my $Attribute ( sort keys %Attributes ) {
1144                    next ATTRIBUTE if defined $Clone->{$Attribute};    # skip redefined values
1145
1146                    $Clone->{$Attribute} = $Attributes{$Attribute};
1147                }
1148
1149                my $Value = $Self->SettingEffectiveValueGet(
1150                    Value => [
1151                        {
1152                            Item => [$Clone],
1153                        },
1154                    ],
1155                    Objects   => \%Objects,
1156                    Translate => $Param{Translate},
1157                );
1158
1159                push @Items, $Value;
1160            }
1161            else {
1162                $Item->{Content} //= '';
1163
1164                # Remove empty space at start and the end (with new lines).
1165                $Item->{Content} =~ s{^\n\s*(.*?)\n\s*$}{$1}gsmx;
1166
1167                push @Items, $Item->{Content};
1168            }
1169        }
1170        $Result = \@Items;
1171    }
1172
1173    return $Result;
1174}
1175
1176=head2 SettingRender()
1177
1178Wrapper for Kernel::Output::HTML::SysConfig::SettingRender() - Returns the specific HTML for the setting.
1179
1180    my $HTMLStr = $SysConfigObject->SettingRender(
1181        Setting   => {
1182            Name             => 'Setting Name',
1183            XMLContentParsed => $XMLParsedToPerl,
1184            EffectiveValue   => "Product 6",        # or a complex structure
1185            DefaultValue     => "Product 5",        # or a complex structure
1186            IsAjax           => 1,                  # (optional) is AJAX request. Default 0.
1187            # ...
1188        },
1189        RW => 1,                                    # (optional) Allow editing. Default 0.
1190    );
1191
1192Returns:
1193
1194    $HTMLStr = '<div class="Setting"><div class "Field"...</div></div>'        # or false in case of an error
1195
1196=cut
1197
1198sub SettingRender {
1199    my ( $Self, %Param ) = @_;
1200
1201    return $Kernel::OM->Get('Kernel::Output::HTML::SysConfig')->SettingRender(%Param);
1202}
1203
1204=head2 SettingAddItem()
1205
1206Wrapper for Kernel::Output::HTML::SysConfig::SettingAddItem() - Returns response that is sent when user adds new array/hash item.
1207
1208    my %Result = $SysConfigObject->SettingAddItem(
1209        SettingStructure  => [],         # (required) array that contains structure
1210                                         #  where a new item should be inserted (can be empty)
1211        Setting           => {           # (required) Setting hash (from SettingGet())
1212            'DefaultID' => '8905',
1213            'DefaultValue' => [ 'Item 1', 'Item 2' ],
1214            'Description' => 'Simple array item(Min 1, Max 3).',
1215            'Name' => 'TestArray',
1216            ...
1217        },
1218        Key               => 'HashKey',  # (optional) hash key
1219        IDSuffix          => '_Array3,   # (optional) suffix that will be added to all input/select fields
1220                                         #    (it is used in the JS on Update, during EffectiveValue calculation)
1221        Value             => [           # (optional) Perl structure
1222            {
1223                'Array' => [
1224                    'Item' => [
1225                        {
1226                        'Content' => 'Item 1',
1227                        },
1228                        ...
1229                    ],
1230                ],
1231            },
1232        ],
1233        AddSettingContent => 0,          # (optional) if enabled, result will be inside of div with class "SettingContent"
1234    );
1235
1236Returns:
1237
1238    %Result = (
1239        'Item' => '<div class=\'SettingContent\'>
1240<input type=\'text\' id=\'TestArray_Array4\'
1241        value=\'Default value\' name=\'TestArray\' class=\' Entry\'/></div>',
1242    );
1243
1244    or
1245
1246    %Result = (
1247        'Error' => 'Error description',
1248    );
1249
1250=cut
1251
1252sub SettingAddItem {
1253    my ( $Self, %Param ) = @_;
1254
1255    return $Kernel::OM->Get('Kernel::Output::HTML::SysConfig')->SettingAddItem(%Param);
1256}
1257
1258=head2 SettingsUpdatedList()
1259
1260Checks which settings has been updated from provided Setting list and returns updated values.
1261
1262    my @List = $SysConfigObject->SettingsUpdatedList(
1263        Settings => [                                               # (required) List of settings that needs to be checked
1264            {
1265                SettingName           => 'SettingName',
1266                ChangeTime            => '2017-01-13 11:23:07',
1267                IsLockedByAnotherUser => 0,
1268            },
1269            ...
1270        ],
1271        UserID => 1,                                                # (required) Current user id
1272    );
1273
1274Returns:
1275
1276    @List = [
1277        {
1278            ChangeTime            => '2017-01-07 11:29:38',
1279            IsLockedByAnotherUser => 1,
1280            IsModified            => 1,
1281            SettingName           => 'SettingName',
1282        },
1283        ...
1284    ];
1285
1286=cut
1287
1288sub SettingsUpdatedList {
1289    my ( $Self, %Param ) = @_;
1290
1291    # Check needed stuff.
1292    for my $Needed (qw(Settings UserID)) {
1293        if ( !$Param{$Needed} ) {
1294            $Kernel::OM->Get('Kernel::System::Log')->Log(
1295                Priority => 'error',
1296                Message  => "Need $Needed!",
1297            );
1298            return;
1299        }
1300    }
1301
1302    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
1303
1304    my @Result;
1305
1306    SETTING:
1307    for my $Setting ( @{ $Param{Settings} } ) {
1308        next SETTING if !IsHashRefWithData($Setting);
1309
1310        my %SettingData = $Self->SettingGet(
1311            Name => $Setting->{SettingName},
1312        );
1313
1314        my $LockedUserID = $SysConfigDBObject->DefaultSettingIsLocked(
1315            Name          => $Setting->{SettingName},
1316            GetLockUserID => 1,
1317        );
1318
1319        my $IsLockedByAnotherUser = $LockedUserID ? 1 : 0;
1320
1321        # Skip if setting was locked by current user during AJAX call.
1322        next SETTING if $LockedUserID == $Param{UserID};
1323
1324        my $Updated = $SettingData{ChangeTime} ne $Setting->{ChangeTime};
1325        $Updated ||= $IsLockedByAnotherUser != $Setting->{IsLockedByAnotherUser};
1326
1327        $Setting->{IsLockedByAnotherUser} = $IsLockedByAnotherUser;
1328
1329        next SETTING if !$Updated;
1330
1331        push @Result, $Setting;
1332    }
1333
1334    return @Result;
1335}
1336
1337=head2 SettingEffectiveValueCheck()
1338
1339Check if provided EffectiveValue matches structure defined in DefaultSetting. Also returns EffectiveValue that might be changed.
1340
1341    my %Result = $SysConfigObject->SettingEffectiveValueCheck(
1342        EffectiveValue => 'open',     # (optional)
1343        XMLContentParsed => {         # (required)
1344            Value => [
1345                {
1346                    'Item' => [
1347                        {
1348                            'Content' => "Scalar value",
1349                        },
1350                    ],
1351                },
1352            ],
1353        },
1354        StoreCache            => 1,               # (optional) Store result in the Cache. Default 0.
1355        SettingUID            => 'Default1234'    # (required if StoreCache)
1356        NoValidation          => 1,               # (optional) no value type validation.
1357        CurrentSystemTime     => 1507894796935,   # (optional) Use provided 1507894796935, otherwise calculate
1358        ExpireTime            => 1507894896935,   # (optional) Use provided ExpireTime for cache, otherwise calculate
1359        UserID                => 1,               # (required) UserID
1360    );
1361
1362Returns:
1363
1364    %Result = (
1365        EffectiveValue => 'closed',    # Note that resulting effective value can be different
1366        Success        => 1,
1367        Error          => undef,
1368    );
1369
1370=cut
1371
1372sub SettingEffectiveValueCheck {
1373    my ( $Self, %Param ) = @_;
1374
1375    for my $Needed (qw(XMLContentParsed UserID)) {
1376        if ( !$Param{$Needed} ) {
1377            $Kernel::OM->Get('Kernel::System::Log')->Log(
1378                Priority => 'error',
1379                Message  => "Need $Needed!"
1380            );
1381            return;
1382        }
1383    }
1384
1385    $Param{EffectiveValue} //= '';
1386    $Param{NoValidation}   //= 0;
1387
1388    my $StoreCache = $Param{StoreCache};
1389
1390    if ( $Param{StoreCache} && !$Param{SettingUID} ) {
1391        $Kernel::OM->Get('Kernel::System::Log')->Log(
1392            Priority => 'error',
1393            Message  => "SettingEffectiveValueCheck() called with StoreCache but without SettingUID parameter!"
1394        );
1395        $StoreCache = 0;    # Fallback, do not use cache.
1396    }
1397
1398    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
1399
1400    my $CacheType = 'SysConfigPersistent';
1401    my $CacheKey  = "EffectiveValueCheck::$Param{NoValidation}";
1402    my $SettingKey;
1403
1404    my $Cache;
1405
1406    my $DateTimeObject;
1407
1408    my $CurrentSystemTime = $Param{CurrentSystemTime};
1409
1410    # Get current system time, if not provided.
1411    if ( !$CurrentSystemTime ) {
1412        $DateTimeObject    = $Kernel::OM->Create('Kernel::System::DateTime');
1413        $CurrentSystemTime = $DateTimeObject->ToEpoch();
1414    }
1415
1416    my $ExpireTime = $Param{ExpireTime};
1417
1418    # Get cache expire time, if not provided.
1419    if ( !$ExpireTime ) {
1420        if ( !$DateTimeObject ) {
1421            $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
1422        }
1423
1424        # Set expire date.
1425        $DateTimeObject->Add(
1426            Months => 1,
1427        );
1428
1429        $ExpireTime = $DateTimeObject->ToEpoch();
1430    }
1431
1432    if ( $Param{SettingUID} ) {
1433
1434        my $MainObject     = $Kernel::OM->Get('Kernel::System::Main');
1435        my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
1436
1437        my $ValueString = $Param{EffectiveValue};
1438        if ( ref $ValueString ) {
1439            my $String = $StorableObject->Serialize(
1440                Data => $Param{EffectiveValue},
1441            );
1442            $ValueString = $MainObject->MD5sum(
1443                String => \$String,
1444            );
1445        }
1446
1447        $SettingKey = "$Param{SettingUID}::${ValueString}";
1448
1449        $Cache = $CacheObject->Get(
1450            Type => $CacheType,
1451            Key  => $CacheKey,
1452        );
1453
1454        if ( $Cache && !$Self->{EffectiveValueCheckCacheDeleted} ) {
1455
1456            # Delete all expired keys.
1457            my @ExpiredKeys = grep { $CurrentSystemTime > ( $Cache->{$_}->{ExpireTime} || 0 ) } keys %{$Cache};
1458            delete @{$Cache}{@ExpiredKeys};
1459
1460            if (@ExpiredKeys) {
1461
1462                # Update cache.
1463                $CacheObject->Set(
1464                    Type  => $CacheType,
1465                    Key   => $CacheKey,
1466                    Value => $Cache,
1467                    TTL   => 20 * 24 * 60 * 60,
1468                );
1469            }
1470
1471            # Remember delete in this round
1472            $Self->{EffectiveValueCheckCacheDeleted} = 1;
1473        }
1474
1475        if (
1476            ref $Cache eq 'HASH'
1477            && $Cache->{$SettingKey}
1478            )
1479        {
1480            return %{ $Cache->{$SettingKey} };
1481        }
1482    }
1483
1484    my %Result = (
1485        Success => 0,
1486    );
1487
1488    my %Parameters = %{ $Param{Parameters} || {} };
1489    my $Value      = $Param{XMLContentParsed}->{Value};
1490
1491    if ( $Value->[0]->{Item} || $Value->[0]->{ValueType} ) {
1492
1493        # get ValueType from parent or use default
1494        my $ValueType = $Parameters{ValueType} || $Value->[0]->{ValueType} || 'String';
1495
1496        # ValueType is defined explicitly(override parent definition)
1497        if (
1498            $Value->[0]->{Item}
1499            && $Value->[0]->{Item}->[0]->{ValueType}
1500            )
1501        {
1502            $ValueType = $Value->[0]->{Item}->[0]->{ValueType};
1503        }
1504
1505        my %ForbiddenValueTypes = %{ $Self->{ForbiddenValueTypes} || {} };
1506
1507        if ( !%ForbiddenValueTypes ) {
1508            %ForbiddenValueTypes = $Self->ForbiddenValueTypesGet();
1509            $Self->{ForbiddenValueTypes} = \%ForbiddenValueTypes;
1510        }
1511
1512        my @SkipValueTypes;
1513
1514        for my $Item ( sort keys %ForbiddenValueTypes ) {
1515            if ( !grep { $_ eq $ForbiddenValueTypes{$Item} } @SkipValueTypes ) {
1516                push @SkipValueTypes, @{ $ForbiddenValueTypes{$Item} };
1517            }
1518        }
1519
1520        if (
1521            $Param{NoValidation}
1522            || grep { $_ eq $ValueType } @SkipValueTypes
1523            )
1524        {
1525            $Result{Success}        = 1;
1526            $Result{EffectiveValue} = $Param{EffectiveValue};
1527            return %Result;
1528        }
1529
1530        my $BackendObject = $Self->{ValueTypeBackendObject}->{$ValueType} || '';
1531
1532        if ( !$BackendObject ) {
1533
1534            my $Loaded = $Kernel::OM->Get('Kernel::System::Main')->Require(
1535                "Kernel::System::SysConfig::ValueType::$ValueType",
1536            );
1537
1538            if ( !$Loaded ) {
1539                $Result{Error} = "Kernel::System::SysConfig::ValueType::$ValueType";
1540                return %Result;
1541            }
1542
1543            $BackendObject = $Kernel::OM->Get(
1544                "Kernel::System::SysConfig::ValueType::$ValueType",
1545            );
1546
1547            $Self->{ValueTypeBackendObject}->{$ValueType} = $BackendObject;
1548        }
1549
1550        %Result = $BackendObject->SettingEffectiveValueCheck(%Param);
1551        $Param{EffectiveValue} = $Result{EffectiveValue} if $Result{Success};
1552    }
1553    elsif ( $Value->[0]->{Hash} ) {
1554
1555        if ( ref $Param{EffectiveValue} ne 'HASH' ) {
1556            $Result{Error} = 'Its not a hash!';
1557            return %Result;
1558        }
1559
1560        PARAMETER:
1561        for my $Parameter ( sort keys %{ $Value->[0]->{Hash}->[0] } ) {
1562
1563            next PARAMETER if !grep { $_ eq $Parameter } qw(MinItems MaxItems);
1564
1565            $Parameters{$Parameter} = $Value->[0]->{Hash}->[0]->{$Parameter} || '';
1566        }
1567
1568        if ( $Parameters{MinItems} && $Parameters{MinItems} > keys %{ $Param{EffectiveValue} } ) {
1569            $Result{Error} = "Number of items in hash is less than MinItems($Parameters{MinItems})!";
1570            return %Result;
1571        }
1572
1573        if ( $Parameters{MaxItems} && $Parameters{MaxItems} < keys %{ $Param{EffectiveValue} } ) {
1574            $Result{Error} = "Number of items in hash is more than MaxItems($Parameters{MaxItems})!";
1575            return %Result;
1576        }
1577
1578        my @Items = ();
1579
1580        if (
1581            scalar @{ $Value->[0]->{Hash} }
1582            && $Value->[0]->{Hash}->[0]->{Item}
1583            && ref $Value->[0]->{Hash}->[0]->{Item} eq 'ARRAY'
1584            )
1585        {
1586            @Items = @{ $Value->[0]->{Hash}->[0]->{Item} };
1587        }
1588
1589        my $DefaultItem;
1590
1591        KEY:
1592        for my $Key ( sort keys %{ $Param{EffectiveValue} } ) {
1593            $DefaultItem = $Value->[0]->{Hash}->[0]->{DefaultItem};
1594
1595            if ( $Value->[0]->{Hash}->[0]->{Item} ) {
1596                my @ItemWithSameKey = grep { $Key eq ( $Value->[0]->{Hash}->[0]->{Item}->[$_]->{Key} || '' ) }
1597                    0 .. scalar @{ $Value->[0]->{Hash}->[0]->{Item} };
1598                if ( scalar @ItemWithSameKey ) {
1599                    $DefaultItem = [
1600                        $Value->[0]->{Hash}->[0]->{Item}->[ $ItemWithSameKey[0] ],
1601                    ];
1602                }
1603
1604                my $StructureType;
1605                if ( $DefaultItem->[0]->{Array} ) {
1606                    $StructureType = 'Array';
1607                }
1608                elsif ( $DefaultItem->[0]->{Hash} ) {
1609                    $StructureType = 'Hash';
1610                }
1611
1612                # check if default item is defined in this sub-structure
1613                if (
1614                    $StructureType
1615                    && !$DefaultItem->[0]->{$StructureType}->[0]->{DefaultItem}
1616                    && $Value->[0]->{Hash}->[0]->{DefaultItem}
1617                    && $Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$StructureType}
1618                    && $Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$StructureType}->[0]->{DefaultItem}
1619                    )
1620                {
1621                    # Default Item is not defined here, but it's defined in previous call.
1622                    $DefaultItem->[0]->{$StructureType}->[0]->{DefaultItem} =
1623                        $Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{$StructureType}->[0]->{DefaultItem};
1624                }
1625            }
1626
1627            my $Ref = ref $Param{EffectiveValue}->{$Key};
1628
1629            if ($Ref) {
1630
1631                if ( IsArrayRefWithData($DefaultItem) ) {
1632
1633                    my $KeyNeeded;
1634
1635                    if ( $Ref eq 'HASH' ) {
1636                        $KeyNeeded = 'Hash';
1637                    }
1638                    elsif ( $Ref eq 'ARRAY' ) {
1639                        $KeyNeeded = 'Array';
1640                    }
1641                    else {
1642                        $Result{Error} = "Wrong format!";
1643                        last KEY;
1644                    }
1645
1646                    if ( $DefaultItem->[0]->{Item} ) {
1647
1648                        # So far everything is OK, we need to check deeper (recursive).
1649                        my %SubResult = $Self->_SettingEffectiveValueCheck(
1650                            XMLContentParsed => {
1651                                Value => $DefaultItem,
1652                            },
1653                            EffectiveValue    => $Param{EffectiveValue}->{$Key},
1654                            NoValidation      => $Param{NoValidation},
1655                            CurrentSystemTime => $Param{CurrentSystemTime},
1656                            ExpireTime        => $Param{ExpireTime},
1657                            UserID            => $Param{UserID},
1658                        );
1659                        $Param{EffectiveValue}->{$Key} = $SubResult{EffectiveValue} if $SubResult{Success};
1660
1661                        if ( $SubResult{Error} ) {
1662                            %Result = %SubResult;
1663                            last KEY;
1664                        }
1665                    }
1666                    elsif ( !defined $DefaultItem->[0]->{$KeyNeeded} ) {
1667                        my $ExpectedText = '';
1668
1669                        if ( $DefaultItem->[0]->{Array} ) {
1670                            $ExpectedText = "an array reference!";
1671                        }
1672                        elsif ( $DefaultItem->[0]->{Hash} ) {
1673                            $ExpectedText = "a hash reference!";
1674                        }
1675                        else {
1676                            $ExpectedText = "a scalar!";
1677                        }
1678
1679                        $Result{Error} = "Item with key $Key must be $ExpectedText";
1680                        last KEY;
1681                    }
1682                    else {
1683
1684                        # So far everything is OK, we need to check deeper (recursive).
1685                        my %SubResult = $Self->_SettingEffectiveValueCheck(
1686                            XMLContentParsed => {
1687                                Value => $DefaultItem,
1688                            },
1689                            EffectiveValue    => $Param{EffectiveValue}->{$Key},
1690                            NoValidation      => $Param{NoValidation},
1691                            CurrentSystemTime => $Param{CurrentSystemTime},
1692                            ExpireTime        => $Param{ExpireTime},
1693                            UserID            => $Param{UserID},
1694                        );
1695                        $Param{EffectiveValue}->{$Key} = $SubResult{EffectiveValue} if $SubResult{Success};
1696
1697                        if ( $SubResult{Error} ) {
1698                            %Result = %SubResult;
1699                            last KEY;
1700                        }
1701                    }
1702                }
1703                else {
1704                    # Hash is empty in the Defaults, value should be scalar.
1705                    $Result{Error} = "Item with key $Key must be a scalar!";
1706                    last KEY;
1707                }
1708            }
1709            else {
1710
1711                # scalar value
1712                if ( IsArrayRefWithData($DefaultItem) ) {
1713                    if ( $DefaultItem->[0]->{Item} || $DefaultItem->[0]->{Content} ) {
1714
1715                        # So far everything is OK, we need to check deeper (recursive).
1716                        my %SubResult = $Self->_SettingEffectiveValueCheck(
1717                            XMLContentParsed => {
1718                                Value => [
1719                                    {
1720                                        Item => $DefaultItem,
1721                                    },
1722                                ],
1723                            },
1724                            EffectiveValue    => $Param{EffectiveValue}->{$Key},
1725                            NoValidation      => $Param{NoValidation},
1726                            CurrentSystemTime => $Param{CurrentSystemTime},
1727                            ExpireTime        => $Param{ExpireTime},
1728                            UserID            => $Param{UserID},
1729                        );
1730
1731                        if ( $SubResult{Error} ) {
1732                            %Result = %SubResult;
1733                            last KEY;
1734                        }
1735                        else {
1736                            $Param{EffectiveValue}->{$Key} = $SubResult{EffectiveValue};
1737                        }
1738
1739                    }
1740                    elsif ( $DefaultItem->[0]->{Hash} ) {
1741                        $Result{Error} = "Item with key $Key must be a hash reference!";
1742                        last KEY;
1743                    }
1744                    elsif ( $DefaultItem->[0]->{Array} ) {
1745                        $Result{Error} = "Item with key $Key must be an array reference!";
1746                        last KEY;
1747                    }
1748                }
1749            }
1750        }
1751
1752        # Check which Value type is default
1753        my $DefaultValueTypeDefined = 'String';
1754        if (
1755            $Value->[0]->{Hash}->[0]->{DefaultItem}
1756            && $Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{ValueType}
1757            )
1758        {
1759            $DefaultValueTypeDefined = $Value->[0]->{Hash}->[0]->{DefaultItem}->[0]->{ValueType};
1760        }
1761
1762        # Get persistent keys(items with value type different then value type defined in the DefaultItem)
1763        my @PersistentKeys;
1764        for my $Item ( @{ $Value->[0]->{Hash}->[0]->{Item} } ) {
1765            my $ValueType = $DefaultValueTypeDefined;
1766
1767            if ( $Item->{ValueType} ) {
1768                $ValueType = $Item->{ValueType};
1769            }
1770            elsif (
1771                $Item->{Item}
1772                && $Item->{Item}->[0]->{ValueType}
1773                )
1774            {
1775                $ValueType = $Item->{Item}->[0]->{ValueType};
1776            }
1777
1778            if ( $ValueType ne $DefaultValueTypeDefined && $Item->{Key} ) {
1779                push @PersistentKeys, $Item->{Key};
1780            }
1781        }
1782
1783        # Validate if all persistent keys are present
1784        PERSISTENT_KEY:
1785        for my $Key (@PersistentKeys) {
1786            if ( !defined $Param{EffectiveValue}->{$Key} ) {
1787                $Result{Error} = $Kernel::OM->Get('Kernel::Language')->Translate( "Missing key %s!", $Key );
1788                last PERSISTENT_KEY;
1789            }
1790        }
1791
1792        if ( $Result{Error} ) {
1793            return %Result;
1794        }
1795
1796        $Result{Success} = 1;
1797    }
1798    elsif ( $Value->[0]->{Array} ) {
1799
1800        if ( ref $Param{EffectiveValue} ne 'ARRAY' ) {
1801            $Result{Error} = 'Its not an array!';
1802            return %Result;
1803        }
1804
1805        PARAMETER:
1806        for my $Parameter ( sort keys %{ $Value->[0]->{Array}->[0] } ) {
1807            next PARAMETER if !grep { $_ eq $Parameter } qw(MinItems MaxItems);
1808
1809            $Parameters{$Parameter} = $Value->[0]->{Array}->[0]->{$Parameter} || '';
1810        }
1811
1812        if ( $Parameters{MinItems} && $Parameters{MinItems} > scalar @{ $Param{EffectiveValue} } ) {
1813            $Result{Error} = "Number of items in array is less than MinItems($Parameters{MinItems})!";
1814            return %Result;
1815        }
1816
1817        if ( $Parameters{MaxItems} && $Parameters{MaxItems} < scalar @{ $Param{EffectiveValue} } ) {
1818            $Result{Error} = "Number of items in array is more than MaxItems($Parameters{MaxItems})!";
1819            return %Result;
1820        }
1821
1822        my @Items = ();
1823        if (
1824            scalar @{ $Value->[0]->{Array} }
1825            && $Value->[0]->{Array}->[0]->{Item}
1826            && ref $Value->[0]->{Array}->[0]->{Item} eq 'ARRAY'
1827            )
1828        {
1829            @Items = @{ $Value->[0]->{Array}->[0]->{Item} };
1830        }
1831
1832        my $DefaultItem;
1833
1834        INDEX:
1835        for my $Index ( 0 .. scalar @{ $Param{EffectiveValue} } - 1 ) {
1836
1837            $DefaultItem = $Value->[0]->{Array}->[0]->{DefaultItem};
1838
1839            my $Ref = ref $Param{EffectiveValue}->[$Index];
1840            if ($Ref) {
1841                if ($DefaultItem) {
1842                    my $KeyNeeded;
1843
1844                    if ( $Ref eq 'HASH' ) {
1845                        $KeyNeeded = 'Hash';
1846                    }
1847                    elsif ( $Ref eq 'ARRAY' ) {
1848                        $KeyNeeded = 'Array';
1849                    }
1850                    else {
1851                        $Result{Error} = "Wrong format!";
1852                        last INDEX;
1853                    }
1854
1855                    if ( $DefaultItem->[0]->{Item} ) {
1856
1857                        # So far everything is OK, we need to check deeper (recursive).
1858                        my %SubResult = $Self->_SettingEffectiveValueCheck(
1859                            XMLContentParsed => {
1860                                Value => [
1861                                    {
1862                                        Item => $DefaultItem,
1863                                    },
1864                                ],
1865                            },
1866                            EffectiveValue    => $Param{EffectiveValue}->[$Index],
1867                            NoValidation      => $Param{NoValidation},
1868                            CurrentSystemTime => $Param{CurrentSystemTime},
1869                            ExpireTime        => $Param{ExpireTime},
1870                            UserID            => $Param{UserID},
1871                        );
1872                        $Param{EffectiveValue}->[$Index] = $SubResult{EffectiveValue} if $SubResult{Success};
1873
1874                        if ( $SubResult{Error} ) {
1875                            %Result = %SubResult;
1876                            last INDEX;
1877                        }
1878                    }
1879                    elsif ( !defined $DefaultItem->[0]->{$KeyNeeded} ) {
1880                        my $ExpectedText = '';
1881
1882                        if ( $DefaultItem->[0]->{Array} ) {
1883                            $ExpectedText = "an array reference!";
1884                        }
1885                        elsif ( $DefaultItem->[0]->{Hash} ) {
1886                            $ExpectedText = "a hash reference!";
1887                        }
1888                        elsif ( $DefaultItem->[0]->{Content} ) {
1889                            $ExpectedText = "a scalar!";
1890                        }
1891
1892                        $Result{Error} = "Item with index $Index must be $ExpectedText";
1893                        last INDEX;
1894                    }
1895                    else {
1896
1897                        # So far everything is OK, we need to check deeper (recursive).
1898                        my %SubResult = $Self->_SettingEffectiveValueCheck(
1899                            XMLContentParsed => {
1900                                Value => $DefaultItem,
1901                            },
1902                            EffectiveValue    => $Param{EffectiveValue}->[$Index],
1903                            NoValidation      => $Param{NoValidation},
1904                            CurrentSystemTime => $Param{CurrentSystemTime},
1905                            ExpireTime        => $Param{ExpireTime},
1906                            UserID            => $Param{UserID},
1907                        );
1908                        $Param{EffectiveValue}->[$Index] = $SubResult{EffectiveValue} if $SubResult{Success};
1909
1910                        if ( $SubResult{Error} ) {
1911                            %Result = %SubResult;
1912                            last INDEX;
1913                        }
1914                    }
1915                }
1916                else {
1917
1918                    # Array is empty in the Defaults, value should be scalar.
1919                    $Result{Error} = "Item with index $Index must be a scalar!";
1920                    last INDEX;
1921                }
1922            }
1923            else {
1924
1925                # scalar
1926                if ($DefaultItem) {
1927
1928                    if ( $DefaultItem->[0]->{Item} || $DefaultItem->[0]->{ValueType} ) {
1929
1930                        # Item with ValueType
1931
1932                        # So far everything is OK, we need to check deeper (recursive).
1933                        my %SubResult = $Self->_SettingEffectiveValueCheck(
1934                            XMLContentParsed => {
1935                                Value => [
1936                                    {
1937                                        Item => $DefaultItem,
1938                                    },
1939                                ],
1940                            },
1941                            EffectiveValue    => $Param{EffectiveValue}->[$Index],
1942                            NoValidation      => $Param{NoValidation},
1943                            CurrentSystemTime => $Param{CurrentSystemTime},
1944                            ExpireTime        => $Param{ExpireTime},
1945                            UserID            => $Param{UserID},
1946                        );
1947                        $Param{EffectiveValue}->[$Index] = $SubResult{EffectiveValue} if $SubResult{Success};
1948
1949                        if ( $SubResult{Error} ) {
1950                            %Result = %SubResult;
1951                            last INDEX;
1952                        }
1953                    }
1954                    elsif ( $DefaultItem->[0]->{Hash} ) {
1955                        $Result{Error} = "Item with index $Index must be a hash reference!";
1956                        last INDEX;
1957                    }
1958                    elsif ( $DefaultItem->[0]->{Array} ) {
1959                        $Result{Error} = "Item with index $Index must be an array reference!";
1960                        last INDEX;
1961                    }
1962                }
1963            }
1964        }
1965        if ( $Result{Error} ) {
1966            return %Result;
1967        }
1968
1969        $Result{Success} = 1;
1970    }
1971
1972    if ( $Result{Success} ) {
1973        $Result{EffectiveValue} = $Param{EffectiveValue};
1974    }
1975
1976    $Result{ExpireTime} = $ExpireTime;
1977
1978    if ($StoreCache) {
1979
1980        $Cache->{$SettingKey} = \%Result;
1981
1982        $CacheObject->Set(
1983            Type  => $CacheType,
1984            Key   => $CacheKey,
1985            Value => $Cache,
1986            TTL   => 20 * 24 * 60 * 60,
1987        );
1988    }
1989
1990    return %Result;
1991}
1992
1993=head2 SettingReset()
1994
1995Reset the modified value to the default value.
1996
1997    my $Result = $SysConfigObject->SettingReset(
1998        Name                  => 'Setting Name',                # (required) Setting name
1999        TargetUserID          => 2,                             # (optional) UserID for settings in AgentPreferences
2000                                                                # or
2001        ExclusiveLockGUID     => $LockingString,                # (optional) the GUID used to locking the setting
2002        UserID                => 1,                             # (required) UserID that creates modification
2003    );
2004
2005Returns:
2006
2007    $Result = 1;        # or false in case of an error
2008
2009=cut
2010
2011sub SettingReset {
2012    my ( $Self, %Param ) = @_;
2013
2014    for my $Needed (qw(Name ExclusiveLockGUID UserID)) {
2015        if ( !$Param{$Needed} ) {
2016            $Kernel::OM->Get('Kernel::System::Log')->Log(
2017                Priority => 'error',
2018                Message  => "Need $Needed!",
2019            );
2020            return;
2021        }
2022    }
2023
2024    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
2025
2026    # Check if the setting exists.
2027    my %DefaultSetting = $SysConfigDBObject->DefaultSettingGet(
2028        Name => $Param{Name},
2029    );
2030
2031    return if !%DefaultSetting;
2032
2033    # Default should be locked.
2034    my $LockedByUser = $SysConfigDBObject->DefaultSettingIsLockedByUser(
2035        DefaultID           => $DefaultSetting{DefaultID},
2036        ExclusiveLockUserID => $Param{UserID},
2037        ExclusiveLockGUID   => $Param{ExclusiveLockGUID},
2038    );
2039
2040    if ( !$LockedByUser ) {
2041        $Kernel::OM->Get('Kernel::System::Log')->Log(
2042            Priority => 'error',
2043            Message  => "Setting $Param{Name} is not locked to this user!",
2044        );
2045        return;
2046    }
2047
2048    my %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
2049        Name     => $Param{Name},
2050        IsGlobal => 1,
2051    );
2052
2053    # Setting already had default value.
2054    return 1 if !%ModifiedSetting;
2055
2056    my %SettingDeployed = $Self->SettingGet(
2057        Name     => $Param{Name},
2058        Deployed => 1,
2059    );
2060
2061    my $IsModified = DataIsDifferent(
2062        Data1 => \$SettingDeployed{EffectiveValue},
2063        Data2 => \$DefaultSetting{EffectiveValue},
2064    ) || 0;
2065
2066    $IsModified ||= $SettingDeployed{IsValid} != $DefaultSetting{IsValid};
2067    $IsModified ||= $SettingDeployed{UserModificationActive} != $DefaultSetting{UserModificationActive};
2068    $ModifiedSetting{IsDirty} = $IsModified ? 1 : 0;
2069
2070    # Copy values from default.
2071    for my $Field (qw(IsValid UserModificationActive EffectiveValue)) {
2072        $ModifiedSetting{$Field} = $DefaultSetting{$Field};
2073    }
2074
2075    # Set reset flag.
2076    $ModifiedSetting{ResetToDefault} = 1;
2077
2078    # Delete modified setting.
2079    my $ResetResult = $SysConfigDBObject->ModifiedSettingUpdate(
2080        %ModifiedSetting,
2081        ExclusiveLockGUID => $Param{ExclusiveLockGUID},
2082        UserID            => $Param{UserID},
2083    );
2084
2085    if ( !$ResetResult ) {
2086        $Kernel::OM->Get('Kernel::System::Log')->Log(
2087            Priority => 'error',
2088            Message  => "System was unable to update Modified setting: $Param{Name}!",
2089        );
2090    }
2091
2092    return $ResetResult;
2093}
2094
2095=head2 ConfigurationTranslatedGet()
2096
2097Returns a hash with all settings and translated metadata.
2098
2099    my %Result = $SysConfigObject->ConfigurationTranslatedGet();
2100
2101Returns:
2102
2103    %Result = (
2104       'ACL::CacheTTL' => {
2105            'Category' => 'OTRS',
2106            'IsInvisible' => '0',
2107            'Metadata' => "ACL::CacheTTL--- '3600'
2108Cache-Zeit in Sekunden f\x{fc}r Datenbank ACL-Backends.",
2109        ...
2110    );
2111
2112=cut
2113
2114sub ConfigurationTranslatedGet {
2115    my ( $Self, %Param ) = @_;
2116
2117    my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
2118    my $CacheObject    = $Kernel::OM->Get('Kernel::System::Cache');
2119
2120    my $CacheType = 'SysConfig';
2121    my $CacheKey  = "ConfigurationTranslatedGet::$LanguageObject->{UserLanguage}";
2122
2123    # Return cache.
2124    my $Cache = $CacheObject->Get(
2125        Type => $CacheType,
2126        Key  => $CacheKey,
2127    );
2128
2129    return %{$Cache} if ref $Cache eq 'HASH';
2130
2131    my @SettingList = $Self->ConfigurationList(
2132        IncludeInvisible => 1,
2133    );
2134
2135    my %Result;
2136
2137    for my $Setting (@SettingList) {
2138
2139        my %SettingTranslated = $Self->_SettingTranslatedGet(
2140            Language => $LanguageObject->{UserLanguage},
2141            Name     => $Setting->{Name},
2142        );
2143
2144        # Append to the result.
2145        $Result{ $Setting->{Name} } = $SettingTranslated{ $Setting->{Name} };
2146    }
2147
2148    $CacheObject->Set(
2149        Type  => $CacheType,
2150        Key   => $CacheKey,
2151        Value => \%Result,
2152        TTL   => $Self->{CacheTTL} || 24 * 60 * 60,
2153    );
2154
2155    return %Result;
2156}
2157
2158=head2 SettingNavigationToPath()
2159
2160Returns path structure for given navigation group.
2161
2162    my @Path = $SysConfigObject->SettingNavigationToPath(
2163        Navigation => 'Frontend::Agent::ToolBarModule',  # (optional)
2164    );
2165
2166Returns:
2167
2168    @Path = (
2169        {
2170            'Value' => 'Frontend',
2171            'Name' => 'Frontend',
2172        },
2173        {
2174            'Value' => 'Frontend::Agent',
2175            'Name' => 'Agent',
2176        },
2177        ...
2178    );
2179
2180=cut
2181
2182sub SettingNavigationToPath {
2183    my ( $Self, %Param ) = @_;
2184
2185    my @NavigationNames = split( '::', $Param{Navigation} );
2186    my @Path;
2187
2188    INDEX:
2189    for my $Index ( 0 .. $#NavigationNames ) {
2190
2191        $Path[$Index]->{Name} = $NavigationNames[$Index];
2192
2193        my @SubArray = @NavigationNames[ 0 .. $Index ];
2194        $Path[$Index]->{Value} = join '::', @SubArray;
2195    }
2196
2197    return @Path;
2198}
2199
2200=head2 ConfigurationTranslatableStrings()
2201
2202Returns a unique list of all translatable strings from the default settings.
2203
2204    my @TranslatableStrings = $SysConfigObject->ConfigurationTranslatableStrings();
2205
2206=cut
2207
2208sub ConfigurationTranslatableStrings {
2209    my ( $Self, %Param ) = @_;
2210
2211    # Reset translation list.
2212    $Self->{ConfigurationTranslatableStrings} = {};
2213
2214    # Get all default settings.
2215    my @SettingsList = $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingListGet();
2216
2217    SETTING:
2218    for my $Setting (@SettingsList) {
2219
2220        next SETTING if !$Setting;
2221        next SETTING if !defined $Setting->{XMLContentParsed};
2222
2223        # Get translatable strings.
2224        $Self->_ConfigurationTranslatableStrings( Data => $Setting->{XMLContentParsed} );
2225
2226    }
2227
2228    my @Strings;
2229    for my $Key ( sort keys %{ $Self->{ConfigurationTranslatableStrings} } ) {
2230        push @Strings, $Key;
2231    }
2232    return @Strings;
2233}
2234
2235=head2 ConfigurationEntitiesGet()
2236
2237Get all entities that are referred in any enabled Setting in complete SysConfig.
2238
2239    my %Result = $SysConfigObject->ConfigurationEntitiesGet();
2240
2241Returns:
2242
2243    %Result = (
2244        'Priority' => {
2245            '3 normal' => [
2246                'Ticket::Frontend::AgentTicketNote###PriorityDefault',
2247                'Ticket::Frontend::AgentTicketPhone###Priority',
2248                ...
2249            ],
2250        },
2251        'Queue' => {
2252            'Postmaster' => [
2253                'Ticket::Frontend::CustomerTicketMessage###QueueDefault',
2254            ],
2255            'Raw' => [
2256                'PostmasterDefaultQueue',
2257            ],
2258        },
2259        ...
2260    );
2261
2262=cut
2263
2264sub ConfigurationEntitiesGet {
2265    my ( $Self, %Param ) = @_;
2266
2267    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
2268
2269    my $CacheType = "SysConfigEntities";
2270    my $CacheKey  = "UsedEntities";
2271
2272    my $CacheData = $CacheObject->Get(
2273        Type => $CacheType,
2274        Key  => $CacheKey,
2275    );
2276
2277    # Return cached data if available.
2278    return %{$CacheData} if $CacheData;
2279
2280    my %Result = ();
2281
2282    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
2283
2284    my @EntitySettings = $SysConfigDBObject->DefaultSettingSearch(
2285        Search => 'ValueEntityType',
2286    );
2287
2288    SETTING:
2289    for my $SettingName (@EntitySettings) {
2290
2291        my %Setting = $SysConfigDBObject->DefaultSettingGet(
2292            Name => $SettingName,
2293        );
2294
2295        # Check if there is modified value.
2296        my %ModifiedSetting = $SysConfigDBObject->ModifiedSettingGet(
2297            Name => $SettingName,
2298        );
2299
2300        if (%ModifiedSetting) {
2301            my $XMLContentParsed = $Self->SettingModifiedXMLContentParsedGet(
2302                ModifiedSetting => \%ModifiedSetting,
2303                DefaultSetting  => \%Setting,
2304            );
2305
2306            $Setting{XMLContentParsed}->{Value} = $XMLContentParsed;
2307        }
2308
2309        %Result = $Self->_ConfigurationEntitiesGet(
2310            Value  => $Setting{XMLContentParsed}->{Value},
2311            Result => \%Result,
2312            Name   => $Setting{XMLContentParsed}->{Name},
2313        );
2314    }
2315
2316    # Cache the results.
2317    $CacheObject->Set(
2318        Type  => $CacheType,
2319        Key   => $CacheKey,
2320        Value => \%Result,
2321        TTL   => 30 * 24 * 60 * 60,
2322    );
2323
2324    return %Result;
2325}
2326
2327=head2 ConfigurationEntityCheck()
2328
2329Check if there are any enabled settings that refers to the provided Entity.
2330
2331    my @Result = $SysConfigObject->ConfigurationEntityCheck(
2332        EntityType  => 'Priority',
2333        EntityName  => '3 normal',
2334    );
2335
2336Returns:
2337
2338    @Result = (
2339        'Ticket::Frontend::AgentTicketNote###PriorityDefault',
2340        'Ticket::Frontend::AgentTicketPhone###Priority',
2341        'Ticket::Frontend::AgentTicketBulk###PriorityDefault',
2342        ...
2343    );
2344
2345=cut
2346
2347sub ConfigurationEntityCheck {
2348    my ( $Self, %Param ) = @_;
2349
2350    for my $Needed (qw(EntityType EntityName)) {
2351        if ( !defined $Param{$Needed} ) {
2352            $Kernel::OM->Get('Kernel::System::Log')->Log(
2353                Priority => 'error',
2354                Message  => "Need $Needed!"
2355            );
2356            return;
2357        }
2358    }
2359    if ( !$Param{EntityType} ) {
2360        $Kernel::OM->Get('Kernel::System::Log')->Log(
2361            Priority => 'error',
2362            Message  => "EntityType is invalid!"
2363        );
2364        return;
2365    }
2366
2367    # If name is an empty string there is nothing to do, return an empty array.
2368    return () if !$Param{EntityName};
2369
2370    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
2371
2372    my $CacheType = "SysConfigEntities";
2373    my $CacheKey  = "ConfigurationEntityCheck::$Param{EntityType}::$Param{EntityName}";
2374
2375    my $CacheData = $CacheObject->Get(
2376        Type => $CacheType,
2377        Key  => $CacheKey,
2378    );
2379
2380    # Return cached data if available.
2381    return @{$CacheData} if $CacheData;
2382
2383    my %EntitySettings = $Self->ConfigurationEntitiesGet();
2384
2385    my @Result = ();
2386
2387    for my $EntityType ( sort keys %EntitySettings ) {
2388
2389        # Check conditions.
2390        if (
2391            $EntityType eq $Param{EntityType}
2392            && $EntitySettings{$EntityType}{ $Param{EntityName} }
2393            )
2394        {
2395            @Result = @{ $EntitySettings{$EntityType}->{ $Param{EntityName} } };
2396        }
2397    }
2398
2399    # Cache the results.
2400    $CacheObject->Set(
2401        Type  => $CacheType,
2402        Key   => $CacheKey,
2403        Value => \@Result,
2404        TTL   => 30 * 24 * 60 * 60,
2405    );
2406
2407    return @Result;
2408}
2409
2410=head2 ConfigurationXML2DB()
2411
2412Load Settings defined in XML files to the database.
2413
2414    my $Success = $SysConfigObject->ConfigurationXML2DB(
2415        UserID    => 1,                  # UserID
2416        Directory => '/some/folder',     # (optional) Provide directory where XML files are stored (default: Kernel/Config/Files/XML).
2417        Force     => 1,                  # (optional) Force Setting update, even if it's locked by another user. Default: 0.
2418        CleanUp   => 1,                  # (optional) Remove all settings that are not present in XML files. Default: 0.
2419    );
2420
2421Returns:
2422
2423    $Success = 1;       # or false in case of an error.
2424
2425=cut
2426
2427sub ConfigurationXML2DB {
2428    my ( $Self, %Param ) = @_;
2429
2430    if ( !$Param{UserID} ) {
2431        $Kernel::OM->Get('Kernel::System::Log')->Log(
2432            Priority => 'error',
2433            Message  => "Need UserID!"
2434        );
2435        return;
2436    }
2437
2438    my $Directory = $Param{Directory} || "$Self->{Home}/Kernel/Config/Files/XML/";
2439
2440    if ( !-e $Directory ) {
2441        $Kernel::OM->Get('Kernel::System::Log')->Log(
2442            Priority => 'error',
2443            Message  => "Directory '$Directory' does not exists",
2444        );
2445
2446        return;
2447    }
2448
2449    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
2450
2451    # Load xml config files, ordered by file name
2452    my @Files = $MainObject->DirectoryRead(
2453        Directory => $Directory,
2454        Filter    => "*.xml",
2455    );
2456
2457    my $CacheObject        = $Kernel::OM->Get('Kernel::System::Cache');
2458    my $SysConfigXMLObject = $Kernel::OM->Get('Kernel::System::SysConfig::XML');
2459    my $SysConfigDBObject  = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
2460
2461    my %SettingsByInit = (
2462        Framework   => [],
2463        Application => [],
2464        Config      => [],
2465        Changes     => [],
2466    );
2467
2468    my %Data;
2469    FILE:
2470    for my $File (@Files) {
2471
2472        my $MD5Sum = $MainObject->MD5sum(
2473            Filename => $File,
2474        );
2475
2476        # Cleanup filename for cache type
2477        my $Filename = $File;
2478        $Filename =~ s{\/\/}{\/}g;
2479        $Filename =~ s{\A .+ Kernel/Config/Files/XML/ (.+)\.xml\z}{$1}msx;
2480        $Filename =~ s{\A .+ scripts/test/sample/SysConfig/XML/ (.+)\.xml\z}{$1}msx;
2481
2482        my $CacheType = 'SysConfigPersistent';
2483        my $CacheKey  = "ConfigurationXML2DB::${Filename}::${MD5Sum}";
2484
2485        my $Cache = $CacheObject->Get(
2486            Type => $CacheType,
2487            Key  => $CacheKey,
2488        );
2489
2490        if (
2491            ref $Cache eq 'HASH'
2492            && $Cache->{Init}
2493            && ref $Cache->{Settings} eq 'ARRAY'
2494            )
2495        {
2496            @{ $SettingsByInit{ $Cache->{Init} } }
2497                = ( @{ $SettingsByInit{ $Cache->{Init} } }, @{ $Cache->{Settings} } );
2498            next FILE;
2499        }
2500
2501        # Read XML file.
2502        my $ConfigFile = $MainObject->FileRead(
2503            Location => $File,
2504            Mode     => 'utf8',
2505            Result   => 'SCALAR',
2506        );
2507        if ( !ref $ConfigFile || !${$ConfigFile} ) {
2508            $Kernel::OM->Get('Kernel::System::Log')->Log(
2509                Priority => 'error',
2510                Message  => "Can't open file $File: $!",
2511            );
2512            next FILE;
2513        }
2514
2515        # Check otrs_config Init attribute.
2516        $$ConfigFile =~ m{<otrs_config.*?init="(.*?)"}gsmx;
2517        my $InitValue = $1;
2518
2519        # Check if InitValue is Valid.
2520        if ( !defined $SettingsByInit{$InitValue} ) {
2521            $Kernel::OM->Get('Kernel::System::Log')->Log(
2522                Priority => 'error',
2523                Message =>
2524                    "Invalid otrs_config Init value ($InitValue)! Allowed values: Framework, Application, Config, Changes.",
2525            );
2526            next FILE;
2527        }
2528
2529        my $XMLFilename = $File;
2530        $XMLFilename =~ s{$Directory(.*\.xml)\z}{$1}gmsx;
2531        $XMLFilename =~ s{\A/}{}gmsx;
2532
2533        # Remove comments.
2534        ${$ConfigFile} =~ s{<!--.*?-->}{}gs;
2535
2536        my @ParsedSettings = $SysConfigXMLObject->SettingListParse(
2537            XMLInput    => ${$ConfigFile},
2538            XMLFilename => $XMLFilename,
2539        );
2540
2541        @{ $SettingsByInit{$InitValue} } = ( @{ $SettingsByInit{$InitValue} }, @ParsedSettings );
2542
2543        # There might be an error parsing file. If we cache the result, error message will not be present.
2544        if (@ParsedSettings) {
2545            $CacheObject->Set(
2546                Key   => $CacheKey,
2547                Type  => $CacheType,
2548                Value => {
2549                    Init     => $InitValue,
2550                    Settings => \@ParsedSettings,
2551                },
2552                TTL => 60 * 60 * 24 * 20,
2553            );
2554        }
2555    }
2556
2557    # Combine everything together in the correct order.
2558    my %Settings;
2559    for my $Init (qw(Framework Application Config Changes)) {
2560        SETTING:
2561        for my $Setting ( @{ $SettingsByInit{$Init} } ) {
2562            my $Name = $Setting->{XMLContentParsed}->{Name};
2563            next SETTING if !$Name;
2564
2565            $Settings{$Name} = $Setting;
2566        }
2567    }
2568
2569    # Find and remove all settings that are in DB, but are not defined in XML files.
2570    if ( $Param{CleanUp} ) {
2571        $Self->_DBCleanUp( Settings => \%Settings );
2572    }
2573
2574    # Lock all settings to be able to update them if needed.
2575    my $ExclusiveLockGUID = $SysConfigDBObject->DefaultSettingLock(
2576        UserID  => $Param{UserID},
2577        LockAll => 1,
2578    );
2579    if ( !$ExclusiveLockGUID ) {
2580        $Kernel::OM->Get('Kernel::System::Log')->Log(
2581            Priority => 'error',
2582            Message  => "System was unable to lock Default Settings ",
2583        );
2584        return;
2585    }
2586
2587    my @SettingList = $Self->ConfigurationList(
2588        IncludeInvisible => 1,
2589    );
2590
2591    my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
2592
2593    if ( !@SettingList ) {
2594        my $Success = $Self->_DefaultSettingAddBulk(
2595            Settings    => \%Settings,
2596            SettingList => \@SettingList,
2597            UserID      => $Param{UserID},
2598        );
2599
2600        return if !$Success;
2601    }
2602    else {
2603
2604        my %DefaultSettingsAdd;
2605
2606        # Build setting list hash, to avoid slow grep expressions.
2607        my %SettingListLookup = map { $_->{Name} => $_ } @SettingList;
2608
2609        # Create/Update settings in DB.
2610        SETTING:
2611        for my $SettingName ( sort keys %Settings ) {
2612
2613            my $DefaultSetting = $SettingListLookup{$SettingName};
2614
2615            if ( IsHashRefWithData($DefaultSetting) ) {
2616
2617                # Compare new Setting XML with the old one (skip if there is no difference).
2618                my $Updated = $Settings{$SettingName}->{XMLContentRaw} ne $DefaultSetting->{XMLContentRaw};
2619                $Updated ||= $Settings{$SettingName}->{XMLFilename} ne $DefaultSetting->{XMLFilename};
2620
2621                next SETTING if !$Updated;
2622
2623                # Create a local clone of the value to prevent any modification.
2624                my $Value = $StorableObject->Clone(
2625                    Data => $Settings{$SettingName}->{XMLContentParsed}->{Value},
2626                );
2627
2628                my $EffectiveValue = $Self->SettingEffectiveValueGet(
2629                    Value => $Value,
2630                );
2631
2632                # Update default setting.
2633                my $Success = $SysConfigDBObject->DefaultSettingUpdate(
2634                    DefaultID      => $DefaultSetting->{DefaultID},
2635                    Name           => $Settings{$SettingName}->{XMLContentParsed}->{Name},
2636                    Description    => $Settings{$SettingName}->{XMLContentParsed}->{Description}->[0]->{Content} || '',
2637                    Navigation     => $Settings{$SettingName}->{XMLContentParsed}->{Navigation}->[0]->{Content} || '',
2638                    IsInvisible    => $Settings{$SettingName}->{XMLContentParsed}->{Invisible} || 0,
2639                    IsReadonly     => $Settings{$SettingName}->{XMLContentParsed}->{ReadOnly} || 0,
2640                    IsRequired     => $Settings{$SettingName}->{XMLContentParsed}->{Required} || 0,
2641                    IsValid        => $Settings{$SettingName}->{XMLContentParsed}->{Valid} || 0,
2642                    HasConfigLevel => $Settings{$SettingName}->{XMLContentParsed}->{ConfigLevel} || 100,
2643                    UserModificationPossible => $Settings{$SettingName}->{XMLContentParsed}->{UserModificationPossible}
2644                        || 0,
2645                    UserModificationActive => $Settings{$SettingName}->{XMLContentParsed}->{UserModificationActive}
2646                        || 0,
2647                    UserPreferencesGroup => $Settings{$SettingName}->{XMLContentParsed}->{UserPreferencesGroup},
2648                    XMLContentRaw        => $Settings{$SettingName}->{XMLContentRaw},
2649                    XMLContentParsed     => $Settings{$SettingName}->{XMLContentParsed},
2650                    XMLFilename          => $Settings{$SettingName}->{XMLFilename},
2651                    EffectiveValue       => $EffectiveValue,
2652                    UserID               => $Param{UserID},
2653                    ExclusiveLockGUID    => $ExclusiveLockGUID,
2654                );
2655                if ( !$Success ) {
2656                    $Kernel::OM->Get('Kernel::System::Log')->Log(
2657                        Priority => 'error',
2658                        Message =>
2659                            "DefaultSettingUpdate failed for Config Item: $SettingName!",
2660                    );
2661                }
2662
2663                my @ModifiedList = $SysConfigDBObject->ModifiedSettingListGet(
2664                    Name => $Settings{$SettingName}->{XMLContentParsed}->{Name},
2665                );
2666
2667                for my $ModifiedSetting (@ModifiedList) {
2668
2669                    # So far everything is OK, if the structure or values does
2670                    # not match anymore, modified values must be deleted.
2671                    my %ValueCheckResult = $Self->SettingEffectiveValueCheck(
2672                        EffectiveValue   => $ModifiedSetting->{EffectiveValue},
2673                        XMLContentParsed => $Settings{$SettingName}->{XMLContentParsed},
2674                        SettingUID       => $ModifiedSetting->{SettingUID},
2675                        StoreCache       => 1,
2676                        UserID           => $Param{UserID},
2677                    );
2678
2679                    if ( !$ValueCheckResult{Success} ) {
2680
2681                        $SysConfigDBObject->ModifiedSettingDelete(
2682                            ModifiedID => $ModifiedSetting->{ModifiedID},
2683                        );
2684
2685                        $Kernel::OM->Get('Kernel::System::Log')->Log(
2686                            Priority => 'error',
2687                            Message  => $ValueCheckResult{Error},
2688                        );
2689                    }
2690                }
2691            }
2692            else {
2693
2694                # Create a local clone of the value to prevent any modification.
2695                my $Value = $StorableObject->Clone(
2696                    Data => $Settings{$SettingName}->{XMLContentParsed}->{Value},
2697                );
2698
2699                my $EffectiveValue = $Self->SettingEffectiveValueGet(
2700                    Value => $Value,
2701                );
2702
2703                $DefaultSettingsAdd{ $Settings{$SettingName}->{XMLContentParsed}->{Name} } = {
2704                    Name           => $Settings{$SettingName}->{XMLContentParsed}->{Name},
2705                    Description    => $Settings{$SettingName}->{XMLContentParsed}->{Description}->[0]->{Content} || '',
2706                    Navigation     => $Settings{$SettingName}->{XMLContentParsed}->{Navigation}->[0]->{Content} || '',
2707                    IsInvisible    => $Settings{$SettingName}->{XMLContentParsed}->{Invisible} || 0,
2708                    IsReadonly     => $Settings{$SettingName}->{XMLContentParsed}->{ReadOnly} || 0,
2709                    IsRequired     => $Settings{$SettingName}->{XMLContentParsed}->{Required} || 0,
2710                    IsValid        => $Settings{$SettingName}->{XMLContentParsed}->{Valid} || 0,
2711                    HasConfigLevel => $Settings{$SettingName}->{XMLContentParsed}->{ConfigLevel} || 100,
2712                    UserModificationPossible => $Settings{$SettingName}->{XMLContentParsed}->{UserModificationPossible}
2713                        || 0,
2714                    UserModificationActive => $Settings{$SettingName}->{XMLContentParsed}->{UserModificationActive}
2715                        || 0,
2716                    UserPreferencesGroup => $Settings{$SettingName}->{XMLContentParsed}->{UserPreferencesGroup},
2717                    XMLContentRaw        => $Settings{$SettingName}->{XMLContentRaw},
2718                    XMLContentParsed     => $Settings{$SettingName}->{XMLContentParsed},
2719                    XMLFilename          => $Settings{$SettingName}->{XMLFilename},
2720                    EffectiveValue       => $EffectiveValue,
2721                    NoCleanup            => 1,
2722                    UserID               => $Param{UserID},
2723                };
2724
2725                # Delete individual cache.
2726                $CacheObject->Delete(
2727                    Type => 'SysConfigDefault',
2728                    Key  => 'DefaultSettingGet::' . $Settings{$SettingName}->{XMLContentParsed}->{Name},
2729                );
2730            }
2731        }
2732
2733        if (%DefaultSettingsAdd) {
2734            my $Success = $Self->_DefaultSettingAddBulk(
2735                Settings    => \%DefaultSettingsAdd,
2736                SettingList => \@SettingList,
2737                UserID      => $Param{UserID},
2738            );
2739            return if !$Success;
2740        }
2741    }
2742
2743    # Unlock all the settings so they can be locked again afterwards.
2744    $SysConfigDBObject->DefaultSettingUnlock(
2745        UnlockAll => 1,
2746    );
2747
2748    return 1;
2749}
2750
2751=head2 ConfigurationNavigationTree()
2752
2753Returns navigation tree in the hash format.
2754
2755    my %Result = $SysConfigObject->ConfigurationNavigationTree(
2756        RootNavigation         => 'Parent',     # (optional) If provided only sub groups of the root navigation are returned.
2757        UserModificationActive => 1,            # (optional) Return settings that can be modified on user level only.
2758        IsValid                => 1,            # (optional) By default, display all settings.
2759        Category               => 'OTRS'        # (optional)
2760    );
2761
2762Returns:
2763
2764    %Result = (
2765        'Core' => {
2766            'Core::Cache' => {},
2767            'Core::CustomerCompany' => {},
2768            'Core::CustomerUser' => {},
2769            'Core::Daemon' => {
2770                'Core::Daemon::ModuleRegistration' => {},
2771            },
2772            ...
2773        'Crypt' =>{
2774            ...
2775        },
2776        ...
2777    );
2778
2779=cut
2780
2781sub ConfigurationNavigationTree {
2782    my ( $Self, %Param ) = @_;
2783
2784    $Param{RootNavigation}         //= '';
2785    $Param{UserModificationActive} //= '0';
2786
2787    my $CacheType = 'SysConfigNavigation';
2788    my $CacheKey  = "NavigationTree::$Param{RootNavigation}::$Param{UserModificationActive}";
2789    if ( defined $Param{IsValid} ) {
2790        if ( $Param{IsValid} ) {
2791            $CacheKey .= '::Valid';
2792        }
2793        else {
2794            $CacheKey .= '::Invalid';
2795        }
2796    }
2797    if ( defined $Param{Category} && $Param{Category} ) {
2798        if ( $Param{Category} eq 'All' ) {
2799            delete $Param{Category};
2800        }
2801        else {
2802            $CacheKey .= "::Category=$Param{Category}";
2803        }
2804    }
2805
2806    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
2807
2808    my $Cache = $CacheObject->Get(
2809        Type => $CacheType,
2810        Key  => $CacheKey,
2811    );
2812
2813    return %{$Cache} if ref $Cache eq 'HASH';
2814
2815    my %CategoryOptions;
2816    if ( $Param{Category} ) {
2817        my %Categories = $Self->ConfigurationCategoriesGet();
2818        if ( $Categories{ $Param{Category} } ) {
2819            %CategoryOptions = (
2820                Category      => $Param{Category},
2821                CategoryFiles => $Categories{ $Param{Category} }->{Files},
2822            );
2823        }
2824    }
2825
2826    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
2827
2828    # Get all default settings
2829    my @SettingsRaw = $SysConfigDBObject->DefaultSettingListGet(
2830        %CategoryOptions,
2831        UserModificationActive => $Param{UserModificationActive} || undef,
2832        IsValid                => $Param{IsValid},
2833    );
2834
2835    # For AgentPreference take into account which settings are Forbidden to update by user or disabled when counting
2836    #   settings. See bug#13488 (https://bugs.otrs.org/show_bug.cgi?id=13488).
2837    if ( $Param{Action} && $Param{Action} eq 'AgentPreferences' ) {
2838
2839        # Get List of all modified settings which are valid and forbidden to update by user.
2840        my @ForbiddenSettings = $SysConfigDBObject->ModifiedSettingListGet(
2841            %CategoryOptions,
2842            UserModificationActive => 0,
2843            IsValid                => 1,
2844        );
2845
2846        # Get List of all modified settings which are invalid and allowed to update by user.
2847        my @InvalidSettings = $SysConfigDBObject->ModifiedSettingListGet(
2848            %CategoryOptions,
2849            UserModificationActive => 1,
2850            IsValid                => 0,
2851        );
2852
2853        my @ModifiedSettings;
2854        for my $Setting (@SettingsRaw) {
2855            push @ModifiedSettings, $Setting
2856                if !grep { $_->{Name} eq $Setting->{Name} } ( @ForbiddenSettings, @InvalidSettings );
2857        }
2858        @SettingsRaw = @ModifiedSettings;
2859
2860        # Add settings which by default are not UserModifiedActive and are changed, to the navigation list
2861        #   in preference screen. Please see bug#13489 for more information.
2862        @ModifiedSettings = $SysConfigDBObject->ModifiedSettingListGet(
2863            %CategoryOptions,
2864            UserModificationActive => 1,
2865            IsValid                => 1,
2866        );
2867        for my $Setting (@ModifiedSettings) {
2868            my %DefaultSetting = $SysConfigDBObject->DefaultSettingGet(
2869                Name => $Setting->{Name},
2870            );
2871            if ( !grep { $_->{Name} eq $DefaultSetting{Name} } @SettingsRaw ) {
2872                push @SettingsRaw, \%DefaultSetting;
2873            }
2874        }
2875    }
2876
2877    my @Settings;
2878
2879    # Skip invisible settings from the navigation tree
2880    SETTING:
2881    for my $Setting (@SettingsRaw) {
2882        next SETTING if $Setting->{IsInvisible};
2883
2884        push @Settings, {
2885            Name       => $Setting->{Name},
2886            Navigation => $Setting->{Navigation},
2887        };
2888    }
2889
2890    my %Result = ();
2891
2892    my @RootNavigation;
2893    if ( $Param{RootNavigation} ) {
2894        @RootNavigation = split "::", $Param{RootNavigation};
2895    }
2896
2897    # Remember ancestors.
2898    for my $Index ( 1 .. $#RootNavigation ) {
2899        $RootNavigation[$Index] = $RootNavigation[ $Index - 1 ] . '::' . $RootNavigation[$Index];
2900    }
2901
2902    SETTING:
2903    for my $Setting (@Settings) {
2904        next SETTING if !$Setting->{Navigation};
2905        my @Path = split "::", $Setting->{Navigation};
2906
2907        # Remember ancestors.
2908        for my $Index ( 1 .. $#Path ) {
2909            $Path[$Index] = $Path[ $Index - 1 ] . '::' . $Path[$Index];
2910        }
2911
2912        # Check if RootNavigation matches current setting.
2913        for my $Index ( 0 .. $#RootNavigation ) {
2914            next SETTING if !$Path[$Index];
2915
2916            if ( $RootNavigation[$Index] ne $Path[$Index] ) {
2917                next SETTING;
2918            }
2919        }
2920
2921        # Remove root groups from Path.
2922        for my $Index ( 0 .. $#RootNavigation ) {
2923            shift @Path;
2924        }
2925
2926        %Result = $Self->_NavigationTree(
2927            Tree  => \%Result,
2928            Array => \@Path,
2929        );
2930    }
2931
2932 # Until now we have structure of the Navigation tree without sub-node count. We need this number to disable
2933 # click on empty nodes. We could implement that in the _NavigationTree, but it's not efficient(loop of 1800+ settings).
2934 # Instead, we extend result in the _NavigationTreeNodeCount.
2935    %Result = $Self->_NavigationTreeNodeCount(
2936        Tree     => \%Result,
2937        Settings => \@Settings,
2938    );
2939
2940    # Cache the results.
2941    $CacheObject->Set(
2942        Type  => $CacheType,
2943        Key   => $CacheKey,
2944        Value => \%Result,
2945        TTL   => 30 * 24 * 60 * 60,
2946    );
2947
2948    return %Result;
2949}
2950
2951=head2 ConfigurationListGet()
2952
2953Returns list of settings that matches provided parameters.
2954
2955    my @List = $SysConfigObject->ConfigurationListGet(
2956        Navigation           => 'SomeNavigationGroup',  # (optional) limit to the settings that have provided navigation
2957        TargetUserID         => 2,                      # (optional) if provided, system returns setting for particular user only,
2958                                                        #       otherwise, returns global setting list
2959        IsValid              => 1,                      # (optional) by default returns valid and invalid settings.
2960        Invisible            => 0,                      # (optional) Include Invisible settings. By default, not included.
2961        UserPreferencesGroup => 'Advanced',             # (optional) filter list by group.
2962        Translate            => 0,                      # (optional) Translate translatable string in EffectiveValue. Default 0.
2963        OverriddenInXML      => 1,                      # (optional) Consider changes made in Perl files. Default 0. Use it in modules only!
2964        UserID               => 1,                      # Required if OverriddenInXML is set.
2965    );
2966
2967Returns:
2968
2969    @List = (
2970        {
2971            DefaultID                => 123,
2972            ModifiedID               => 456,     # if modified
2973            Name                     => "ProductName",
2974            Description              => "Defines the name of the application ...",
2975            Navigation               => "ASimple::Path::Structure",
2976            IsInvisible              => 1,
2977            IsReadonly               => 0,
2978            IsRequired               => 1,
2979            IsValid                  => 1,
2980            HasConfigLevel           => 200,
2981            UserModificationPossible => 0,          # 1 or 0
2982            UserModificationActive   => 0,          # 1 or 0
2983            UserPreferencesGroup     => 'Advanced', # optional
2984            XMLContentRaw            => "The XML structure as it is on the config file",
2985            XMLContentParsed         => "XML parsed to Perl",
2986            XMLFilename              => "Daemon.xml",
2987            EffectiveValue           => "Product 6",
2988            DefaultValue             => "Product 5",
2989            IsModified               => 1,       # 1 or 0
2990            IsDirty                  => 1,       # 1 or 0
2991            ExclusiveLockGUID        => 'A32CHARACTERLONGSTRINGFORLOCKING',
2992            ExclusiveLockUserID      => 1,
2993            ExclusiveLockExpiryTime  => '2016-05-29 11:09:04',
2994            CreateTime               => "2016-05-29 11:04:04",
2995            ChangeTime               => "2016-05-29 11:04:04",
2996            OverriddenFileName        => 'ZZZDefauls.pm'
2997        },
2998        {
2999            DefaultID     => 321,
3000            Name          => 'FieldName',
3001            # ...
3002            CreateTime    => '2010-09-11 10:08:00',
3003            ChangeTime    => '2011-01-01 01:01:01',
3004        },
3005        # ...
3006    );
3007
3008=cut
3009
3010sub ConfigurationListGet {
3011    my ( $Self, %Param ) = @_;
3012
3013    if ( $Param{OverriddenInXML} && !$Param{UserID} ) {
3014        $Kernel::OM->Get('Kernel::System::Log')->Log(
3015            Priority => 'error',
3016            Message  => 'UserID is needed when OverriddenInXML is set!',
3017        );
3018        return;
3019    }
3020
3021    my $ConfigObject      = $Kernel::OM->Get('Kernel::Config');
3022    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
3023
3024    $Param{Translate} //= 0;    # don't translate by default
3025
3026    my %CategoryOptions;
3027    if ( $Param{Category} ) {
3028        my %Categories = $Self->ConfigurationCategoriesGet();
3029        if ( $Categories{ $Param{Category} } ) {
3030            %CategoryOptions = (
3031                Category      => $Param{Category},
3032                CategoryFiles => $Categories{ $Param{Category} }->{Files},
3033            );
3034        }
3035    }
3036
3037    # Get all default settings for this navigation group.
3038    my @ConfigurationList = $SysConfigDBObject->DefaultSettingListGet(
3039        Navigation               => $Param{Navigation},
3040        UserModificationPossible => $Param{TargetUserID} ? 1 : undef,
3041        UserPreferencesGroup     => $Param{UserPreferencesGroup} || undef,
3042        IsInvisible              => $Param{Invisible} ? undef : 0,
3043        %CategoryOptions,
3044    );
3045
3046    my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
3047
3048    # Update setting values with the modified settings.
3049    SETTING:
3050    for my $Setting (@ConfigurationList) {
3051
3052        if ( $Param{TargetUserID} ) {
3053            my %SettingGlobal = $Self->SettingGet(
3054                Name      => $Setting->{Name},
3055                IsGlobal  => 1,
3056                Translate => $Param{Translate},
3057            );
3058
3059            if ( %SettingGlobal && $SettingGlobal{ModifiedID} ) {
3060
3061                # There is modified setting, but we need last deployed version.
3062                my %SettingDeployed = $SysConfigDBObject->ModifiedSettingVersionGetLast(
3063                    Name => $Setting->{Name},
3064                );
3065
3066                if ( !IsHashRefWithData( \%SettingDeployed ) ) {
3067                    %SettingDeployed = $Self->SettingGet(
3068                        Name      => $Setting->{Name},
3069                        Default   => 1,
3070                        Translate => $Param{Translate},
3071                    );
3072                }
3073
3074                $Setting = {
3075                    %SettingGlobal,
3076                    %SettingDeployed,
3077                };
3078            }
3079            else {
3080
3081                # Use default value.
3082                my %SettingDefault = $Self->SettingGet(
3083                    Name      => $Setting->{Name},
3084                    Default   => 1,
3085                    Translate => $Param{Translate},
3086                );
3087
3088                $Setting = \%SettingDefault;
3089            }
3090        }
3091
3092        # Remember default value.
3093        $Setting->{DefaultValue} = $Setting->{EffectiveValue};
3094        if ( ref $Setting->{EffectiveValue} ) {
3095            $Setting->{DefaultValue} = $StorableObject->Clone(
3096                Data => $Setting->{EffectiveValue},
3097            );
3098        }
3099
3100        my %ModifiedSetting = $Self->SettingGet(
3101            Name            => $Setting->{Name},
3102            TargetUserID    => $Param{TargetUserID} // undef,
3103            Translate       => $Param{Translate},
3104            OverriddenInXML => $Param{OverriddenInXML},
3105            UserID          => $Param{UserID},
3106        );
3107
3108        # Skip if setting is invalid.
3109        next SETTING if !IsHashRefWithData( \%ModifiedSetting );
3110        next SETTING if !defined $ModifiedSetting{EffectiveValue};
3111
3112        # Mark setting as modified.
3113        my $IsModified = DataIsDifferent(
3114            Data1 => \$Setting->{EffectiveValue},
3115            Data2 => \$ModifiedSetting{EffectiveValue},
3116        ) || 0;
3117
3118        $IsModified ||= $ModifiedSetting{IsValid} != $Setting->{IsValid};
3119        $IsModified ||= $ModifiedSetting{UserModificationActive} != $Setting->{UserModificationActive};
3120
3121        $Setting->{IsModified} = $IsModified ? 1 : 0;
3122
3123        # Update setting attributes.
3124        ATTRIBUTE:
3125        for my $Attribute (
3126            qw(ModifiedID IsValid UserModificationActive UserPreferencesGroup EffectiveValue IsDirty ChangeTime XMLContentParsed SettingUID OverriddenFileName)
3127            )
3128        {
3129            next ATTRIBUTE if !defined $ModifiedSetting{$Attribute};
3130
3131            $Setting->{$Attribute} = $ModifiedSetting{$Attribute};
3132        }
3133    }
3134
3135    if ( defined $Param{IsValid} ) {
3136        @ConfigurationList = grep { $_->{IsValid} == $Param{IsValid} } @ConfigurationList;
3137    }
3138
3139    if ( $Param{TargetUserID} ) {
3140
3141        # List contains all settings that can be activated. Get only those that are really activated.
3142        @ConfigurationList = grep { $_->{UserModificationActive} } @ConfigurationList;
3143    }
3144
3145    return @ConfigurationList;
3146}
3147
3148=head2 ConfigurationList()
3149
3150Wrapper of Kernel::System::SysConfig::DB::DefaultSettingList() - Get list of all settings.
3151
3152    my @SettingList = $SysConfigObject->ConfigurationList();
3153
3154Returns:
3155
3156    @SettingList = (
3157        {
3158            DefaultID => '123',
3159            Name      => 'SettingName1',
3160            IsDirty   => 1,
3161        },
3162        {
3163            DefaultID => '124',
3164            Name      => 'SettingName2',
3165            IsDirty   => 0
3166        },
3167        ...
3168    );
3169
3170=cut
3171
3172sub ConfigurationList {
3173    my ( $Self, %Param ) = @_;
3174
3175    return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingList(%Param);
3176}
3177
3178=head2 ConfigurationInvalidList()
3179
3180Returns list of enabled settings that have invalid effective value.
3181
3182    my @List = $SysConfigObject->ConfigurationInvalidList(
3183        CachedOnly  => 0,   # (optional) Default 0. If enabled, system will return cached value.
3184                            #                 If there is no cache yet, system will return empty list, but
3185                            #                 it will also trigger async call to generate cache.
3186        Undeployed  => 1,   # (optional) Default 0. Check settings that are not deployed as well.
3187        NoCache     => 1,   # (optional) Default 0. If enabled, system won't check the cached value.
3188    );
3189
3190Returns:
3191
3192    @List = ( "Setting1", "Setting5", ... );
3193
3194=cut
3195
3196sub ConfigurationInvalidList {
3197    my ( $Self, %Param ) = @_;
3198
3199    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
3200
3201    my $CacheType = 'SysConfig';
3202    my $CacheKey  = 'ConfigurationInvalidList';
3203
3204    if ( $Param{Undeployed} ) {
3205        $CacheKey .= '::Undeployed';
3206    }
3207
3208    # Return cache.
3209    my $Cache = $CacheObject->Get(
3210        Type => $CacheType,
3211        Key  => $CacheKey,
3212    );
3213
3214    return @{$Cache} if ref $Cache eq 'ARRAY' && !$Param{NoCache};
3215
3216    if ( $Param{CachedOnly} ) {
3217
3218        # There is no cache but caller expects quick answer. Return empty array, but create cache in async call.
3219        $Self->AsyncCall(
3220            ObjectName               => 'Kernel::System::SysConfig',
3221            FunctionName             => 'ConfigurationInvalidList',
3222            FunctionParams           => {},
3223            MaximumParallelInstances => 1,
3224        );
3225
3226        return ();
3227    }
3228
3229    my @SettingsEnabled = $Self->ConfigurationListGet(
3230        IsValid   => 1,
3231        Translate => 0,
3232    );
3233
3234    my @InvalidSettings;
3235
3236    my $DateTimeObject    = $Kernel::OM->Create('Kernel::System::DateTime');
3237    my $CurrentSystemTime = $DateTimeObject->ToEpoch();
3238
3239    $DateTimeObject->Add(
3240        Months => 1,
3241    );
3242    my $ExpireTime = $DateTimeObject->ToEpoch();
3243
3244    for my $Setting (@SettingsEnabled) {
3245        my %SettingData = $Self->SettingGet(
3246            Name     => $Setting->{Name},
3247            Deployed => $Param{Undeployed} ? undef : 1,
3248            NoCache  => $Param{NoCache},
3249        );
3250
3251        my %EffectiveValueCheck = $Self->SettingEffectiveValueCheck(
3252            EffectiveValue    => $SettingData{EffectiveValue},
3253            XMLContentParsed  => $Setting->{XMLContentParsed},
3254            CurrentSystemTime => $CurrentSystemTime,
3255            ExpireTime        => $ExpireTime,
3256            UserID            => 1,
3257        );
3258
3259        if ( $EffectiveValueCheck{Error} ) {
3260            push @InvalidSettings, $Setting->{Name};
3261        }
3262    }
3263
3264    $CacheObject->Set(
3265        Type  => $CacheType,
3266        Key   => $CacheKey,
3267        Value => \@InvalidSettings,
3268        TTL   => 60 * 60,             # 1 hour
3269    );
3270
3271    return @InvalidSettings;
3272}
3273
3274=head2 ConfigurationDeploy()
3275
3276Write configuration items from database into a perl module file.
3277
3278    my %Result = $SysConfigObject->ConfigurationDeploy(
3279        Comments            => "Some comments",     # (optional)
3280        NoValidation        => 0,                   # (optional) 1 or 0, default 0, skips settings validation
3281        UserID              => 123,                 # if ExclusiveLockGUID is used, UserID must match the user that creates the lock
3282        Force               => 1,                   # (optional) proceed even if lock is set to another user
3283        NotDirty            => 1,                   # (optional) do not use any values from modified dirty settings
3284        AllSettings         => 1,                   # (optional) use dirty modified settings from all users
3285        DirtySettings       => [                    # (optional) use only this dirty modified settings from the current user
3286            'SettingOne',
3287            'SettingTwo',
3288        ],
3289    );
3290
3291Returns:
3292
3293    %Result = (
3294        Success => 1,           # Deployment successful.
3295    );
3296
3297    or
3298
3299    %Result = (
3300        Success => 0,           # Deployment failed.
3301        Error   => 'Error...',  # Error message (if available)
3302    );
3303
3304=cut
3305
3306sub ConfigurationDeploy {
3307    my ( $Self, %Param ) = @_;
3308
3309    my %Result = (
3310        Success => 0,
3311    );
3312
3313    if ( !$Param{UserID} ) {
3314        $Kernel::OM->Get('Kernel::System::Log')->Log(
3315            Priority => 'error',
3316            Message  => "Need UserID!",
3317        );
3318
3319        return %Result;
3320    }
3321    if ( !IsPositiveInteger( $Param{UserID} ) ) {
3322        $Kernel::OM->Get('Kernel::System::Log')->Log(
3323            Priority => 'error',
3324            Message  => "UserID is invalid!",
3325        );
3326        return %Result;
3327    }
3328
3329    my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
3330
3331    if ( $Param{AllSettings} ) {
3332        $Param{NotDirty}      = 0;
3333        $Param{DirtySettings} = undef;
3334    }
3335    elsif ( $Param{NotDirty} ) {
3336        $Param{AllSettings}   = 0;
3337        $Param{DirtySettings} = undef;
3338    }
3339
3340    $Param{NoValidation} //= 0;
3341
3342    my $BasePath = 'Kernel/Config/Files/';
3343
3344    # Parameter 'FileName' is intentionally not documented in the API as it is only used for testing.
3345    my $TargetPath = $BasePath . ( $Param{FileName} || "ZZZAAuto.pm" );
3346
3347    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
3348
3349    my @UserDirtySettings = $SysConfigDBObject->ModifiedSettingListGet(
3350        IsDirty  => 1,
3351        ChangeBy => $Param{UserID},
3352    );
3353    my %UserDirtySettingsLookup = map { $_->{Name} => 1 } @UserDirtySettings;
3354
3355    # Determine dirty settings to deploy (if not specified get all dirty settings from current user).
3356    if ( !$Param{DirtySettings} && !$Param{AllSettings} && !$Param{NotDirty} ) {
3357        @{ $Param{DirtySettings} } = keys %UserDirtySettingsLookup;
3358    }
3359    elsif ( $Param{DirtySettings} && !$Param{AllSettings} && !$Param{NotDirty} ) {
3360
3361        my @DirtySettings;
3362
3363        SETTING:
3364        for my $Setting ( @{ $Param{DirtySettings} } ) {
3365            next SETTING if !$UserDirtySettingsLookup{$Setting};
3366            push @DirtySettings, $Setting;
3367        }
3368
3369        $Param{DirtySettings} = \@DirtySettings;
3370    }
3371
3372    my @DirtyDefaultList = $SysConfigDBObject->DefaultSettingList(
3373        IsDirty => 1,
3374    );
3375
3376    my $AddNewDeployment;
3377
3378    # Check if deployment is really needed
3379    if ( $Param{NotDirty} ) {
3380
3381        # Check if default settings are to be deployed
3382        if (@DirtyDefaultList) {
3383            $AddNewDeployment = 1;
3384        }
3385    }
3386    elsif ( $Param{AllSettings} ) {
3387
3388        # Check if default settings are to be deployed
3389        if (@DirtyDefaultList) {
3390            $AddNewDeployment = 1;
3391        }
3392        else {
3393
3394            my @DirtyModifiedSettings = $SysConfigDBObject->ModifiedSettingListGet(
3395                IsDirty => 1,
3396            );
3397
3398            # Check if modified settings are to be deployed
3399            if (@DirtyModifiedSettings) {
3400                $AddNewDeployment = 1;
3401            }
3402        }
3403    }
3404    elsif ( $Param{DirtySettings} ) {
3405
3406        # Check if default settings or user modified settings are to be deployed
3407        if ( @DirtyDefaultList || IsArrayRefWithData( $Param{DirtySettings} ) ) {
3408            $AddNewDeployment = 1;
3409        }
3410    }
3411
3412    # In case none of the previous options applied and there is no deployment in the database,
3413    #   a new deployment is needed.
3414    my %LastDeployment = $SysConfigDBObject->DeploymentGetLast();
3415    if ( !%LastDeployment ) {
3416        $AddNewDeployment = 1;
3417    }
3418
3419    my $EffectiveValueStrg = '';
3420
3421    my @Settings = $Self->_GetSettingsToDeploy(
3422        %Param,
3423        NoCache => %LastDeployment ? 0 : 1,    # do not cache only during initial rebuild config
3424    );
3425
3426    my %EffectiveValueCheckResult;
3427
3428    my $MainObject     = $Kernel::OM->Get('Kernel::System::Main');
3429    my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
3430
3431    my $DateTimeObject    = $Kernel::OM->Create('Kernel::System::DateTime');
3432    my $CurrentSystemTime = $DateTimeObject->ToEpoch();
3433
3434    $DateTimeObject->Add(
3435        Months => 1,
3436    );
3437    my $ExpireTime = $DateTimeObject->ToEpoch();
3438
3439    SETTING:
3440    for my $CurrentSetting (@Settings) {
3441        next SETTING if !$CurrentSetting->{IsValid};
3442
3443        my %EffectiveValueCheck = $Self->SettingEffectiveValueCheck(
3444            XMLContentParsed  => $CurrentSetting->{XMLContentParsed},
3445            EffectiveValue    => $CurrentSetting->{EffectiveValue},
3446            NoValidation      => $Param{NoValidation},
3447            SettingUID        => $CurrentSetting->{SettingUID},
3448            CurrentSystemTime => $CurrentSystemTime,
3449            ExpireTime        => $ExpireTime,
3450            UserID            => $Param{UserID},
3451        );
3452
3453        # Instead of caching for each setting(1800+), skip caching, but remember results and cache only once.
3454        my $ValueString = $Param{EffectiveValue} // '';
3455        if ( ref $ValueString ) {
3456            my $String = $StorableObject->Serialize(
3457                Data => $Param{EffectiveValue},
3458            );
3459            $ValueString = $MainObject->MD5sum(
3460                String => \$String,
3461            );
3462        }
3463
3464        my $SettingKey = "$CurrentSetting->{SettingUID}::${ValueString}";
3465        $EffectiveValueCheckResult{$SettingKey} = \%EffectiveValueCheck;
3466
3467        next SETTING if $EffectiveValueCheck{Success};
3468
3469        # Check if setting is overridden, in this case allow deployment.
3470        my $OverriddenFileName = $Self->OverriddenFileNameGet(
3471            SettingName    => $CurrentSetting->{Name},
3472            UserID         => $Param{UserID},
3473            EffectiveValue => $CurrentSetting->{EffectiveValue},
3474        );
3475
3476        if ($OverriddenFileName) {
3477
3478            # Setting in the DB has invalid value, but it's overridden in perl file.
3479
3480            # Note: This check can't be moved to the SettingEffectiveValueCheck(), since it works with Cache,
3481            # so if perl file is updated, changes won't be reflected.
3482            next SETTING;
3483        }
3484
3485        $Kernel::OM->Get('Kernel::System::Log')->Log(
3486            Priority => 'error',
3487            Message  => "Setting $CurrentSetting->{Name} Effective value is not correct: $EffectiveValueCheck{Error}",
3488        );
3489
3490        $Result{Error} = $LanguageObject->Translate( "Invalid setting: %s", $CurrentSetting->{Name} );
3491        return %Result;
3492    }
3493
3494    # Set cache for SettingEffectiveValueCheck().
3495    $Self->_SettingEffectiveValueCheckCacheSet(
3496        Value        => \%EffectiveValueCheckResult,
3497        NoValidation => $Param{NoValidation},
3498    );
3499
3500    # Combine settings effective values into a perl string
3501    if ( IsArrayRefWithData( \@Settings ) ) {
3502        $EffectiveValueStrg = $Self->_EffectiveValues2PerlFile(
3503            Settings   => \@Settings,
3504            TargetPath => $TargetPath,
3505        );
3506        if ( !defined $EffectiveValueStrg ) {
3507            $Kernel::OM->Get('Kernel::System::Log')->Log(
3508                Priority => 'error',
3509                Message  => "Could not combine settings values into a perl hash",
3510            );
3511
3512            $Result{Error} = $LanguageObject->Translate("Could not combine settings values into a perl hash.");
3513            return %Result;
3514        }
3515    }
3516
3517    # Force new deployment if current DB settings are different from the last deployment.
3518    if ( !$AddNewDeployment ) {
3519
3520        # Remove CurrentDeploymentID line for easy compare.
3521        my $LastDeploymentStrg = $LastDeployment{EffectiveValueStrg};
3522        $LastDeploymentStrg =~ s{\$Self->\{'CurrentDeploymentID'\} [ ] = [ ] '\d+';\n}{}msx;
3523
3524        if ( $EffectiveValueStrg ne $LastDeploymentStrg ) {
3525            $AddNewDeployment = 1;
3526        }
3527    }
3528
3529    if ($AddNewDeployment) {
3530
3531        # Lock the deployment to be able add it to the DB.
3532        my $ExclusiveLockGUID = $SysConfigDBObject->DeploymentLock(
3533            UserID => $Param{UserID},
3534            Force  => $Param{Force},
3535        );
3536        if ( !$ExclusiveLockGUID ) {
3537            $Kernel::OM->Get('Kernel::System::Log')->Log(
3538                Priority => 'error',
3539                Message  => "Can not lock the deployment for UserID '$Param{UserID}'!",
3540            );
3541
3542            $Result{Error} = $LanguageObject->Translate(
3543                "Can not lock the deployment for UserID '%s'!",
3544                $Param{UserID},
3545            );
3546            return %Result;
3547        }
3548
3549        # Get system time stamp (string formatted).
3550        my $DateTimeObject = $Kernel::OM->Create(
3551            'Kernel::System::DateTime'
3552        );
3553        my $TimeStamp = $DateTimeObject->ToString();
3554
3555        my $HandleSettingsSuccess = $Self->_HandleSettingsToDeploy(
3556            %Param,
3557            DeploymentExclusiveLockGUID => $ExclusiveLockGUID,
3558            DeploymentTimeStamp         => $TimeStamp,
3559        );
3560
3561        my $DeploymentID;
3562
3563        if ($HandleSettingsSuccess) {
3564
3565            # Add a new deployment in the DB.
3566            $DeploymentID = $SysConfigDBObject->DeploymentAdd(
3567                Comments            => $Param{Comments},
3568                EffectiveValueStrg  => \$EffectiveValueStrg,
3569                ExclusiveLockGUID   => $ExclusiveLockGUID,
3570                DeploymentTimeStamp => $TimeStamp,
3571                UserID              => $Param{UserID},
3572            );
3573            if ( !$DeploymentID ) {
3574                $Kernel::OM->Get('Kernel::System::Log')->Log(
3575                    Priority => 'error',
3576                    Message  => "Could not create the deployment in the DB!",
3577                );
3578
3579            }
3580        }
3581
3582        # Unlock the deployment, so new deployments can be added afterwards.
3583        my $Unlock = $SysConfigDBObject->DeploymentUnlock(
3584            ExclusiveLockGUID => $ExclusiveLockGUID,
3585            UserID            => $Param{UserID},
3586        );
3587        if ( !$Unlock ) {
3588            $Kernel::OM->Get('Kernel::System::Log')->Log(
3589                Priority => 'error',
3590                Message  => "Could not remove deployment lock for UserID '$Param{UserID}'",
3591            );
3592        }
3593
3594        # Make sure to return on errors after we unlock the deployment.
3595        if ( !$HandleSettingsSuccess || !$DeploymentID ) {
3596            return %Result;
3597        }
3598
3599        # If setting is updated on global level, check all user specific settings, maybe it's needed
3600        #   to remove duplicates.
3601        if ( $Self->can('UserConfigurationResetToGlobal') ) {    # OTRS Business Solution™
3602
3603            my @DeployedSettings;
3604            if ( $Param{DirtySettings} ) {
3605                @DeployedSettings = @{ $Param{DirtySettings} };
3606            }
3607            else {
3608                for my $Setting (@Settings) {
3609                    push @DeployedSettings, $Setting->{Name};
3610                }
3611            }
3612
3613            if ( scalar @DeployedSettings ) {
3614                $Self->UserConfigurationResetToGlobal(
3615                    Settings => \@DeployedSettings,
3616                    UserID   => $Param{UserID},
3617                );
3618            }
3619        }
3620
3621        my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
3622
3623        # Delete categories cache.
3624        $CacheObject->Delete(
3625            Type => 'SysConfig',
3626            Key  => 'ConfigurationCategoriesGet',
3627        );
3628
3629        $CacheObject->Delete(
3630            Type => 'SysConfig',
3631            Key  => 'ConfigurationInvalidList',
3632        );
3633        $CacheObject->Delete(
3634            Type => 'SysConfig',
3635            Key  => 'ConfigurationInvalidList::Undeployed',
3636        );
3637    }
3638    else {
3639        $EffectiveValueStrg = $LastDeployment{EffectiveValueStrg};
3640    }
3641
3642    # Base folder for deployment could be not present.
3643    if ( !-d $BasePath ) {
3644        mkdir $BasePath;
3645    }
3646
3647    $Result{Success} = $Self->_FileWriteAtomic(
3648        Filename => "$Self->{Home}/$TargetPath",
3649        Content  => \$EffectiveValueStrg,
3650    );
3651
3652    return %Result;
3653}
3654
3655=head2 ConfigurationDeployList()
3656
3657Get deployment list with complete data.
3658
3659    my @List = $SysConfigObject->ConfigurationDeployList();
3660
3661Returns:
3662
3663    @List = (
3664        {
3665            DeploymentID       => 123,
3666            Comments           => 'Some Comments',
3667            EffectiveValueStrg => $SettingEffectiveValues,      # String with the value of all settings,
3668                                                                #   as seen in the Perl configuration file.
3669            CreateTime         => "2016-05-29 11:04:04",
3670            CreateBy           => 123,
3671        },
3672        {
3673            DeploymentID       => 456,
3674            Comments           => 'Some Comments',
3675            EffectiveValueStrg => $SettingEffectiveValues2,     # String with the value of all settings,
3676                                                                #   as seen in the Perl configuration file.
3677            CreateTime         => "2016-05-29 12:00:01",
3678            CreateBy           => 123,
3679        },
3680        # ...
3681    );
3682
3683=cut
3684
3685sub ConfigurationDeployList {
3686    my ( $Self, %Param ) = @_;
3687
3688    return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DeploymentListGet();
3689}
3690
3691=head2 ConfigurationDeploySync()
3692Updates C<ZZZAAuto.pm> to the latest deployment found in the database.
3693
3694    my $Success = $SysConfigObject->ConfigurationDeploySync();
3695
3696=cut
3697
3698sub ConfigurationDeploySync {
3699    my ( $Self, %Param ) = @_;
3700
3701    my $Home       = $Self->{Home};
3702    my $TargetPath = "$Home/Kernel/Config/Files/ZZZAAuto.pm";
3703
3704    if ( -e $TargetPath ) {
3705        if ( !require $TargetPath ) {
3706            $Kernel::OM->Get('Kernel::System::Log')->Log(
3707                Priority => 'error',
3708                Message  => "Could not load $TargetPath, $1",
3709            );
3710            return;
3711        }
3712
3713        do $TargetPath;
3714    }
3715
3716    $Kernel::OM->ObjectsDiscard(
3717        Objects => [ 'Kernel::Config', ],
3718    );
3719
3720    my $CurrentDeploymentID = $Kernel::OM->Get('Kernel::Config')->Get('CurrentDeploymentID') || 0;
3721
3722    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
3723
3724    # Check that all deployments are valid, but wait if there are deployments in add procedure
3725    my $CleanupSuccess;
3726    TRY:
3727    for my $Try ( 1 .. 40 ) {
3728        $CleanupSuccess = $SysConfigDBObject->DeploymentListCleanup();
3729        last TRY if !$CleanupSuccess;
3730        last TRY if $CleanupSuccess == 1;
3731        sleep .5;
3732    }
3733    if ( $CleanupSuccess != 1 ) {
3734        $Kernel::OM->Get('Kernel::System::Log')->Log(
3735            Priority => 'error',
3736            Message  => "There are invalid deployments in the database that could not be removed!",
3737        );
3738        return;
3739    }
3740
3741    my %LastDeployment = $SysConfigDBObject->DeploymentGetLast();
3742
3743    if ( !%LastDeployment ) {
3744        $Kernel::OM->Get('Kernel::System::Log')->Log(
3745            Priority => 'error',
3746            Message  => "No deployments found in Database!",
3747        );
3748        return;
3749    }
3750
3751    if ( $CurrentDeploymentID ne $LastDeployment{DeploymentID} ) {
3752
3753        # Write latest deployment to ZZZAAuto.pm
3754        my $EffectiveValueStrg = $LastDeployment{EffectiveValueStrg};
3755        my $Success            = $Self->_FileWriteAtomic(
3756            Filename => $TargetPath,
3757            Content  => \$EffectiveValueStrg,
3758        );
3759
3760        return if !$Success;
3761    }
3762
3763    # Sync also user specific settings (if available).
3764    return 1 if !$Self->can('UserConfigurationDeploySync');    # OTRS Business Solution™
3765    $Self->UserConfigurationDeploySync();
3766
3767    return 1;
3768}
3769
3770=head2 ConfigurationDeployCleanup()
3771
3772Cleanup old deployments from the database.
3773
3774    my $Success = $SysConfigObject->ConfigurationDeployCleanup();
3775
3776Returns:
3777
3778    $Success = 1;       # or false in case of an error
3779
3780=cut
3781
3782sub ConfigurationDeployCleanup {
3783    my ( $Self, %Param ) = @_;
3784
3785    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
3786
3787    my @List    = $SysConfigDBObject->DeploymentListGet();
3788    my @ListIDs = map { $_->{DeploymentID} } @List;
3789
3790    my $RemainingDeploments = $Kernel::OM->Get('Kernel::Config')->Get('SystemConfiguration::MaximumDeployments') // 20;
3791    @ListIDs = splice( @ListIDs, $RemainingDeploments );
3792
3793    DEPLOYMENT:
3794    for my $DeploymentID (@ListIDs) {
3795
3796        my $Success = $SysConfigDBObject->DeploymentDelete(
3797            DeploymentID => $DeploymentID,
3798        );
3799
3800        if ( !$Success ) {
3801            $Kernel::OM->Get('Kernel::System::Log')->Log(
3802                Priority => 'notice',
3803                Message  => "Was not possible to delete deployment $DeploymentID!",
3804            );
3805            next DEPLOYMENT;
3806        }
3807    }
3808
3809    return 1;
3810}
3811
3812=head2 ConfigurationDeployGet()
3813
3814Wrapper of Kernel::System::SysConfig::DB::DeploymentGet() - Get deployment information.
3815
3816    my %Deployment = $SysConfigObject->ConfigurationDeployGet(
3817        DeploymentID => 123,
3818    );
3819
3820Returns:
3821
3822    %Deployment = (
3823        DeploymentID       => 123,
3824        Comments           => 'Some Comments',
3825        EffectiveValueStrg => $SettingEffectiveValues,      # String with the value of all settings,
3826                                                            #   as seen in the Perl configuration file.
3827        CreateTime         => "2016-05-29 11:04:04",
3828        CreateBy           => 123,
3829    );
3830
3831=cut
3832
3833sub ConfigurationDeployGet {
3834    my ( $Self, %Param ) = @_;
3835
3836    return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DeploymentGet(%Param);
3837}
3838
3839=head2 ConfigurationDeployGetLast()
3840
3841Wrapper of Kernel::System::SysConfig::DBDeploymentGetLast() - Get last deployment information.
3842
3843    my %Deployment = $SysConfigObject->ConfigurationDeployGetLast();
3844
3845Returns:
3846
3847    %Deployment = (
3848        DeploymentID       => 123,
3849        Comments           => 'Some Comments',
3850        EffectiveValueStrg => $SettingEffectiveValues,      # String with the value of all settings,
3851                                                            #   as seen in the Perl configuration file.
3852        CreateTime         => "2016-05-29 11:04:04",
3853        CreateBy           => 123,
3854    );
3855
3856=cut
3857
3858sub ConfigurationDeployGetLast {
3859    my ( $Self, %Param ) = @_;
3860
3861    return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DeploymentGetLast();
3862}
3863
3864=head2 ConfigurationDeploySettingsListGet()
3865
3866Gets full modified settings information contained on a given deployment.
3867
3868    my @List = $SysConfigObject->ConfigurationDeploySettingsListGet(
3869        DeploymentID => 123,
3870    );
3871
3872Returns:
3873
3874    @List = (
3875        {
3876            DefaultID                => 123,
3877            ModifiedID               => 456,
3878            ModifiedVersionID        => 789,
3879            Name                     => "ProductName",
3880            Description              => "Defines the name of the application ...",
3881            Navigation               => "ASimple::Path::Structure",
3882            IsInvisible              => 1,
3883            IsReadonly               => 0,
3884            IsRequired               => 1,
3885            IsValid                  => 1,
3886            HasConfigLevel           => 200,
3887            UserModificationPossible => 0,       # 1 or 0
3888            XMLContentRaw            => "The XML structure as it is on the config file",
3889            XMLContentParsed         => "XML parsed to Perl",
3890            EffectiveValue           => "Product 6",
3891            DefaultValue             => "Product 5",
3892            IsModified               => 1,       # 1 or 0
3893            IsDirty                  => 1,       # 1 or 0
3894            ExclusiveLockGUID        => 'A32CHARACTERLONGSTRINGFORLOCKING',
3895            ExclusiveLockUserID      => 1,
3896            ExclusiveLockExpiryTime  => '2016-05-29 11:09:04',
3897            CreateTime               => "2016-05-29 11:04:04",
3898            ChangeTime               => "2016-05-29 11:04:04",
3899        },
3900        {
3901            DefaultID         => 321,
3902            ModifiedID        => 654,
3903            ModifiedVersionID => 987,
3904             Name             => 'FieldName',
3905            # ...
3906            CreateTime => '2010-09-11 10:08:00',
3907            ChangeTime => '2011-01-01 01:01:01',
3908        },
3909        # ...
3910    );
3911
3912=cut
3913
3914sub ConfigurationDeploySettingsListGet {
3915    my ( $Self, %Param ) = @_;
3916
3917    if ( !$Param{DeploymentID} ) {
3918        $Kernel::OM->Get('Kernel::System::Log')->Log(
3919            Priority => 'error',
3920            Message  => "Need DeploymentID",
3921        );
3922        return;
3923    }
3924
3925    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
3926
3927    # get modified version of this deployment
3928    my %ModifiedVersionList = $SysConfigDBObject->DeploymentModifiedVersionList(
3929        DeploymentID => $Param{DeploymentID},
3930    );
3931
3932    my @ModifiedVersions = sort keys %ModifiedVersionList;
3933
3934    my @Settings;
3935    for my $ModifiedVersionID ( sort @ModifiedVersions ) {
3936
3937        my %Versions;
3938
3939        # Get the modified version.
3940        my %ModifiedSettingVersion = $SysConfigDBObject->ModifiedSettingVersionGet(
3941            ModifiedVersionID => $ModifiedVersionID,
3942        );
3943
3944        # Get default version.
3945        my %DefaultSetting = $SysConfigDBObject->DefaultSettingVersionGet(
3946            DefaultVersionID => $ModifiedSettingVersion{DefaultVersionID},
3947        );
3948
3949        # Update default setting attributes.
3950        for my $Attribute (
3951            qw(ModifiedID IsValid EffectiveValue IsDirty CreateTime ChangeTime)
3952            )
3953        {
3954            $DefaultSetting{$Attribute} = $ModifiedSettingVersion{$Attribute};
3955        }
3956
3957        $DefaultSetting{ModifiedVersionID} = $ModifiedVersionID;
3958
3959        push @Settings, \%DefaultSetting;
3960    }
3961
3962    return @Settings;
3963}
3964
3965=head2 ConfigurationIsDirtyCheck()
3966
3967Check if there are not deployed changes on system configuration.
3968
3969    my $Result = $SysConfigObject->ConfigurationIsDirtyCheck(
3970        UserID => 123,      # optional, the user that changes a modified setting
3971    );
3972
3973Returns:
3974
3975    $Result = 1;    # or 0 if configuration is not dirty.
3976
3977=cut
3978
3979sub ConfigurationIsDirtyCheck {
3980    my ( $Self, %Param ) = @_;
3981
3982    return $Kernel::OM->Get('Kernel::System::SysConfig::DB')->ConfigurationIsDirty(%Param);
3983}
3984
3985=head2 ConfigurationDump()
3986
3987Creates a YAML file with the system configuration settings.
3988
3989    my $ConfigurationDumpYAML = $SysConfigObject->ConfigurationDump(
3990        OnlyValues           => 0,  # optional, default 0, dumps only the setting effective value instead of the whole setting attributes.
3991        SkipDefaultSettings  => 0,  # optional, default 0, do not include default settings
3992        SkipModifiedSettings => 0,  # optional, default 0, do not include modified settings
3993        SkipUserSettings     => 0,  # optional, default 0, do not include user settings
3994        DeploymentID         => 123, # optional, if it is provided the modified settings are retrieved from versions
3995    );
3996
3997Returns:
3998
3999    my $ConfigurationDumpYAML = '---
4000Default:
4001  Setting1:
4002    DefaultID: 23766
4003    Name: Setting1
4004    # ...
4005  Setting2:
4006  # ...
4007Modified:
4008  Setting1
4009    DefaultID: 23776
4010    ModifiedID: 1250
4011    Name: Setting1
4012    # ...
4013  # ...
4014JDoe:
4015  Setting2
4016    DefaultID: 23777
4017    ModifiedID: 1251
4018    Name: Setting2
4019    # ...
4020  # ...
4021# ...
4022
4023or
4024
4025    my $ConfigurationDumpYAML = $SysConfigObject->ConfigurationDump(
4026        OnlyValues => 1,
4027    );
4028
4029Returns:
4030
4031    my $ConfigurationDumpYAML = '---
4032Default:
4033  Setting1: Test
4034  Setting2: Test
4035  # ...
4036Modified:
4037  Setting1: TestUpdate
4038  # ...
4039JDoe:
4040  Setting2: TestUser
4041  # ...
4042# ...
4043';
4044
4045=cut
4046
4047sub ConfigurationDump {
4048    my ( $Self, %Param ) = @_;
4049
4050    my $Result = {};
4051
4052    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
4053
4054    if ( !$Param{SkipDefaultSettings} ) {
4055
4056        my @SettingsList = $SysConfigDBObject->DefaultSettingListGet();
4057
4058        SETTING:
4059        for my $Setting (@SettingsList) {
4060            if ( $Param{OnlyValues} ) {
4061                $Result->{Default}->{ $Setting->{Name} } = $Setting->{EffectiveValue};
4062                next SETTING;
4063            }
4064            $Result->{Default}->{ $Setting->{Name} } = $Setting;
4065        }
4066
4067    }
4068
4069    if ( !$Param{SkipModifiedSettings} || !$Param{SkipUserSettings} ) {
4070        my @SettingsList;
4071
4072        if ( !$Param{DeploymentID} ) {
4073            @SettingsList = $SysConfigDBObject->ModifiedSettingListGet();
4074        }
4075        else {
4076            # Get the modified versions involved into the deployment
4077            my %ModifiedVersionList = $SysConfigDBObject->DeploymentModifiedVersionList(
4078                DeploymentID => $Param{DeploymentID},
4079            );
4080
4081            return if !%ModifiedVersionList;
4082
4083            my @ModifiedVersions = sort keys %ModifiedVersionList;
4084
4085            MODIFIEDVERSIONID:
4086            for my $ModifiedVersionID (@ModifiedVersions) {
4087
4088                my %ModifiedSettingVersion = $SysConfigDBObject->ModifiedSettingVersionGet(
4089                    ModifiedVersionID => $ModifiedVersionID,
4090                );
4091                next MODIFIEDVERSIONID if !%ModifiedSettingVersion;
4092
4093                push @SettingsList, \%ModifiedSettingVersion;
4094            }
4095        }
4096
4097        if ( !$Param{SkipModifiedSettings} ) {
4098            SETTING:
4099            for my $Setting (@SettingsList) {
4100                next SETTING if $Setting->{TargetUserID};
4101
4102                if ( $Param{OnlyValues} ) {
4103                    $Result->{'Modified'}->{ $Setting->{Name} } = $Setting->{EffectiveValue};
4104                    next SETTING;
4105                }
4106                $Result->{'Modified'}->{ $Setting->{Name} } = $Setting;
4107            }
4108        }
4109
4110        if ( !$Param{SkipUserSettings} && $Self->can('UserConfigurationDump') ) {    # OTRS Business Solution™
4111            my %UserSettings = $Self->UserConfigurationDump(
4112                SettingList => \@SettingsList,
4113                OnlyValues  => $Param{OnlyValues},
4114            );
4115            if ( scalar keys %UserSettings ) {
4116                %{$Result} = ( %{$Result}, %UserSettings );
4117            }
4118        }
4119    }
4120
4121    my $YAMLString = $Kernel::OM->Get('Kernel::System::YAML')->Dump(
4122        Data => $Result,
4123    );
4124
4125    return $YAMLString;
4126}
4127
4128=head2 ConfigurationLoad()
4129
4130Takes a YAML file with settings definition and try to import it into the system.
4131
4132    my $Success = $SysConfigObject->ConfigurationLoad(
4133        ConfigurationYAML   => $YAMLString,     # a YAML string in the format of L<ConfigurationDump()>
4134        UserID              => 123,
4135    );
4136
4137=cut
4138
4139sub ConfigurationLoad {
4140    my ( $Self, %Param ) = @_;
4141
4142    for my $Needed (qw(ConfigurationYAML UserID)) {
4143        if ( !$Param{$Needed} ) {
4144            $Kernel::OM->Get('Kernel::System::Log')->Log(
4145                Priority => 'error',
4146                Message  => "Need $Needed!",
4147            );
4148
4149            return;
4150        }
4151    }
4152
4153    my %ConfigurationRaw
4154        = %{ $Kernel::OM->Get('Kernel::System::YAML')->Load( Data => $Param{ConfigurationYAML} ) || {} };
4155
4156    if ( !%ConfigurationRaw ) {
4157        $Kernel::OM->Get('Kernel::System::Log')->Log(
4158            Priority => 'error',
4159            Message  => "ConfigurationYAML is invalid!",
4160        );
4161
4162        return;
4163    }
4164
4165    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
4166
4167    # Get the configuration sections to import (skip Default and non existing users).
4168    my $ValidSections;
4169    my %Configuration;
4170    SECTION:
4171    for my $Section ( sort keys %ConfigurationRaw ) {
4172
4173        next SECTION if $Section eq 'Default';
4174
4175        if ( $Section eq 'Modified' ) {
4176            $Configuration{$Section} = $ConfigurationRaw{$Section};
4177            next SECTION;
4178        }
4179
4180        my $UserID = $UserObject->UserLookup(
4181            UserLogin => $Section,
4182            Silent    => 1,
4183        );
4184
4185        if ( !$UserID ) {
4186            $Kernel::OM->Get('Kernel::System::Log')->Log(
4187                Priority => 'notice',
4188                Message  => "Settings for user $Section could not be added! User does not exists.",
4189            );
4190            next SECTION;
4191        }
4192
4193        $Configuration{$UserID} = $ConfigurationRaw{$Section};
4194
4195    }
4196
4197    # Early return if there is nothing to update.
4198    return 1 if !%Configuration;
4199
4200    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
4201    my $Result            = 1;
4202
4203    SECTION:
4204    for my $Section ( sort keys %Configuration ) {
4205
4206        my $UserID      = '';
4207        my $ScopeString = '(global)';
4208
4209        my $TargetUserID = undef;
4210        if ( lc $Section ne lc 'Modified' ) {
4211            $TargetUserID = $Section;
4212            $UserID       = $Section;
4213            $ScopeString  = "(for user $Section)";
4214        }
4215
4216        SETTINGNAME:
4217        for my $SettingName ( sort keys %{ $Configuration{$Section} } ) {
4218
4219            my %CurrentSetting = $Self->SettingGet(
4220                Name => $SettingName,
4221            );
4222
4223            # Set error in case non existing settings (either default or modified);
4224            if ( !%CurrentSetting ) {
4225                $Result = '-1';
4226                next SETTINGNAME;
4227            }
4228
4229            my $ExclusiveLockGUID = $SysConfigDBObject->DefaultSettingLock(
4230                Name   => $SettingName,
4231                Force  => 1,
4232                UserID => $UserID || $Param{UserID},
4233            );
4234
4235            my $UserModificationActive = $TargetUserID ? undef : $CurrentSetting{UserModificationActive};
4236
4237            my %Result = $Self->SettingUpdate(
4238                Name                   => $SettingName,
4239                IsValid                => $Configuration{$Section}->{$SettingName}->{IsValid},
4240                EffectiveValue         => $Configuration{$Section}->{$SettingName}->{EffectiveValue},
4241                UserModificationActive => $UserModificationActive,
4242                TargetUserID           => $TargetUserID,
4243                ExclusiveLockGUID      => $ExclusiveLockGUID,
4244                UserID                 => $UserID || $Param{UserID},
4245            );
4246            if ( !$Result{Success} ) {
4247                $Kernel::OM->Get('Kernel::System::Log')->Log(
4248                    Priority => 'error',
4249                    Message  => "Setting $SettingName $ScopeString was not correctly updated!",
4250                );
4251
4252                $Result = '-1';
4253            }
4254        }
4255
4256        # Only deploy user specific settings;
4257        next SECTION if !$TargetUserID;
4258        next SECTION if !$Self->can('UserConfigurationDeploy');    # OTRS Business Solution™
4259
4260        # Deploy user configuration requires another package to be installed.
4261        my $Success = $Self->UserConfigurationDeploy(
4262            TargetUserID => $TargetUserID,
4263            UserID       => $Param{UserID},
4264        );
4265
4266    }
4267
4268    return $Result;
4269}
4270
4271=head2 ConfigurationDirtySettingsList()
4272
4273Returns a list of setting names that are dirty.
4274
4275    my @Result = $SysConfigObject->ConfigurationDirtySettingsList(
4276        ChangeBy => 123,
4277    );
4278
4279Returns:
4280
4281    $Result = ['SettingA', 'SettingB', 'SettingC'];
4282
4283=cut
4284
4285sub ConfigurationDirtySettingsList {
4286    my ( $Self, %Param ) = @_;
4287
4288    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
4289
4290    my @DefaultSettingsList = $SysConfigDBObject->DefaultSettingListGet(
4291        IsDirty => 1,
4292    );
4293    @DefaultSettingsList = map { $_->{Name} } @DefaultSettingsList;
4294
4295    my @ModifiedSettingsList = $SysConfigDBObject->ModifiedSettingListGet(
4296        IsDirty  => 1,
4297        IsGlobal => 1,
4298        ChangeBy => $Param{ChangeBy} || undef,
4299    );
4300    @ModifiedSettingsList = map { $_->{Name} } @ModifiedSettingsList;
4301
4302    # Combine Default and Modified dirty settings.
4303    my @ListNames = ( @DefaultSettingsList, @ModifiedSettingsList );
4304    my %Names     = map { $_ => 1 } @ListNames;
4305    @ListNames = sort keys %Names;
4306
4307    return @ListNames;
4308}
4309
4310=head2 ConfigurationLockedSettingsList()
4311
4312Returns a list of setting names that are locked in general or by user.
4313
4314    my @Result = $SysConfigObject->ConfigurationLockedSettingsList(
4315        ExclusiveLockUserID       => 2, # Optional, ID of the user for which the default setting is locked
4316    );
4317
4318Returns:
4319
4320    $Result = ['SettingA', 'SettingB', 'SettingC'];
4321
4322=cut
4323
4324sub ConfigurationLockedSettingsList {
4325    my ( $Self, %Param ) = @_;
4326
4327    my @DefaultSettingsList = $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DefaultSettingListGet(
4328        Locked => 1,
4329    );
4330
4331    return if !IsArrayRefWithData( \@DefaultSettingsList );
4332
4333    if ( $Param{ExclusiveLockUserID} ) {
4334        @DefaultSettingsList
4335            = map { $_->{Name} } grep { $_->{ExclusiveLockUserID} eq $Param{ExclusiveLockUserID} } @DefaultSettingsList;
4336    }
4337    else {
4338        @DefaultSettingsList = map { $_->{Name} } @DefaultSettingsList;
4339    }
4340
4341    return @DefaultSettingsList;
4342}
4343
4344=head2 ConfigurationSearch()
4345
4346Returns a list of setting names.
4347
4348    my @Result = $SysConfigObject->ConfigurationSearch(
4349        Search           => 'The search string', # (optional)
4350        Category         => 'OTRS'               # (optional)
4351        IncludeInvisible => 1,                   # (optional) Default 0.
4352    );
4353
4354Returns:
4355
4356    $Result = ['SettingA', 'SettingB', 'SettingC'];
4357
4358=cut
4359
4360sub ConfigurationSearch {
4361    my ( $Self, %Param ) = @_;
4362
4363    if ( !$Param{Search} && !$Param{Category} ) {
4364        $Kernel::OM->Get('Kernel::System::Log')->Log(
4365            Priority => 'error',
4366            Message  => "Search or Category is needed",
4367        );
4368        return;
4369    }
4370
4371    $Param{Search}   ||= '';
4372    $Param{Category} ||= '';
4373
4374    my $Search = lc $Param{Search};
4375
4376    my %Settings = $Self->ConfigurationTranslatedGet(
4377        IncludeInvisible => $Param{IncludeInvisible},
4378    );
4379
4380    my %Result;
4381
4382    SETTING:
4383    for my $SettingName ( sort keys %Settings ) {
4384
4385        # check category
4386        if (
4387            $Param{Category}                    &&
4388            $Param{Category} ne 'All'           &&
4389            $Settings{$SettingName}->{Category} &&
4390            $Settings{$SettingName}->{Category} ne $Param{Category}
4391            )
4392        {
4393            next SETTING;
4394        }
4395
4396        # check invisible
4397        if (
4398            !$Param{IncludeInvisible}
4399            && $Settings{$SettingName}->{IsInvisible}
4400            )
4401        {
4402            next SETTING;
4403        }
4404
4405        if ( !$Param{Search} ) {
4406            $Result{$SettingName} = 1;
4407            next SETTING;
4408        }
4409
4410        $Param{Search} =~ s{ +}{ }g;
4411        my @SearchTerms = split ' ', $Param{Search};
4412
4413        SEARCHTERM:
4414        for my $SearchTerm (@SearchTerms) {
4415
4416            # do not search with the x and/or g modifier as it would produce wrong search results!
4417            if ( $Settings{$SettingName}->{Metadata} =~ m{\Q$SearchTerm\E}msi ) {
4418
4419                next SEARCHTERM if $Result{$SettingName};
4420
4421                $Result{$SettingName} = 1;
4422            }
4423        }
4424    }
4425
4426    return ( sort keys %Result );
4427}
4428
4429=head2 ConfigurationCategoriesGet()
4430
4431Returns a list of categories with their filenames.
4432
4433    my %Categories = $SysConfigObject->ConfigurationCategoriesGet();
4434
4435Returns:
4436
4437    %Categories = (
4438        All => {
4439            DisplayName => 'All Settings',
4440            Files => [],
4441        },
4442        OTRS => {
4443            DisplayName => 'OTRS',
4444            Files       => ['Calendar.xml', CloudServices.xml', 'Daemon.xml', 'Framework.xml', 'GenericInterface.xml', 'ProcessManagement.xml', 'Ticket.xml' ],
4445        },
4446        # ...
4447    );
4448
4449=cut
4450
4451sub ConfigurationCategoriesGet {
4452    my ( $Self, %Param ) = @_;
4453
4454    my $CacheType = 'SysConfig';
4455    my $CacheKey  = 'ConfigurationCategoriesGet';
4456
4457    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
4458
4459    # Return cache.
4460    my $Cache = $CacheObject->Get(
4461        Type => $CacheType,
4462        Key  => $CacheKey,
4463    );
4464
4465    return %{$Cache} if ref $Cache eq 'HASH';
4466
4467    # Set framework files.
4468    my %Result = (
4469        All => {
4470            DisplayName => Translatable('All Settings'),
4471            Files       => [],
4472        },
4473        OTRS => {
4474            DisplayName => 'OTRS',
4475            Files       => [
4476                'Calendar.xml', 'CloudServices.xml', 'Daemon.xml', 'Framework.xml',
4477                'GenericInterface.xml', 'ProcessManagement.xml', 'Ticket.xml',
4478            ],
4479        },
4480    );
4481
4482    my @PackageList = $Kernel::OM->Get('Kernel::System::Package')->RepositoryList();
4483
4484    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
4485
4486    # Get files from installed packages.
4487    PACKAGE:
4488    for my $Package (@PackageList) {
4489
4490        next PACKAGE if !$Package->{Name}->{Content};
4491        next PACKAGE if !IsArrayRefWithData( $Package->{Filelist} );
4492
4493        my @XMLFiles;
4494        FILE:
4495        for my $File ( @{ $Package->{Filelist} } ) {
4496            $File->{Location} =~ s/\/\//\//g;
4497            my $Search = 'Kernel/Config/Files/XML/';
4498
4499            if ( substr( $File->{Location}, 0, length $Search ) ne $Search ) {
4500                next FILE;
4501            }
4502
4503            my $Filename = $File->{Location};
4504            $Filename =~ s{\AKernel/Config/Files/XML/(.+\.xml)\z}{$1}msxi;
4505            push @XMLFiles, $Filename;
4506        }
4507
4508        next PACKAGE if !@XMLFiles;
4509
4510        my $PackageName = $Package->{Name}->{Content};
4511        my $DisplayName = $ConfigObject->Get("SystemConfiguration::Category::Name::$PackageName") || $PackageName;
4512
4513        # special treatment for OTRS Business Solution™
4514        if ( $DisplayName eq 'OTRSBusiness' ) {
4515            $DisplayName = 'OTRS Business Solution™';
4516        }
4517
4518        $Result{$PackageName} = {
4519            DisplayName => $DisplayName,
4520            Files       => \@XMLFiles,
4521        };
4522    }
4523
4524    $CacheObject->Set(
4525        Type  => $CacheType,
4526        Key   => $CacheKey,
4527        Value => \%Result,
4528        TTL   => 24 * 3600 * 30,    # 1 month
4529    );
4530
4531    return %Result;
4532}
4533
4534=head2 ForbiddenValueTypesGet()
4535
4536Returns a hash of forbidden value types.
4537
4538    my %ForbiddenValueTypes = $SysConfigObject->ForbiddenValueTypesGet();
4539
4540Returns:
4541
4542    %ForbiddenValueType = (
4543        String => [],
4544        Select => ['Option'],
4545        ...
4546    );
4547
4548=cut
4549
4550sub ForbiddenValueTypesGet {
4551    my ( $Self, %Param ) = @_;
4552
4553    my $CacheType = 'SysConfig';
4554    my $CacheKey  = 'ForbiddenValueTypesGet';
4555
4556    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
4557
4558    # Return cache.
4559    my $Cache = $CacheObject->Get(
4560        Type => $CacheType,
4561        Key  => $CacheKey,
4562    );
4563
4564    return %{$Cache} if ref $Cache eq 'HASH';
4565
4566    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
4567
4568    my @ValueTypes = $Self->_ValueTypesList();
4569
4570    my %Result;
4571
4572    for my $ValueType (@ValueTypes) {
4573
4574        my $Loaded = $MainObject->Require(
4575            "Kernel::System::SysConfig::ValueType::$ValueType",
4576        );
4577
4578        if ($Loaded) {
4579            my $ValueTypeObject = $Kernel::OM->Get(
4580                "Kernel::System::SysConfig::ValueType::$ValueType",
4581            );
4582
4583            my @ForbiddenValueTypes = $ValueTypeObject->ForbiddenValueTypes();
4584            if ( scalar @ForbiddenValueTypes ) {
4585                $Result{$ValueType} = \@ForbiddenValueTypes;
4586            }
4587        }
4588    }
4589
4590    $CacheObject->Set(
4591        Type  => $CacheType,
4592        Key   => $CacheKey,
4593        Value => \%Result,
4594        TTL   => 24 * 3600,    # 1 day
4595    );
4596
4597    return %Result;
4598}
4599
4600=head2 ValueAttributeList()
4601
4602Returns a hash of forbidden value types.
4603
4604    my @ValueAttributeList = $SysConfigObject->ValueAttributeList();
4605
4606Returns:
4607
4608    @ValueAttributeList = (
4609        "Content",
4610        "SelectedID",
4611    );
4612
4613=cut
4614
4615sub ValueAttributeList {
4616
4617    my ( $Self, %Param ) = @_;
4618
4619    my $CacheType = 'SysConfig';
4620    my $CacheKey  = 'ValueAttributeList';
4621
4622    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
4623
4624    # Return cache.
4625    my $Cache = $CacheObject->Get(
4626        Type => $CacheType,
4627        Key  => $CacheKey,
4628    );
4629
4630    return @{$Cache} if ref $Cache eq 'ARRAY';
4631
4632    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
4633
4634    my @ValueTypes = $Self->_ValueTypesList();
4635
4636    my @Result;
4637
4638    for my $ValueType (@ValueTypes) {
4639
4640        my $Loaded = $MainObject->Require(
4641            "Kernel::System::SysConfig::ValueType::$ValueType",
4642        );
4643
4644        if ($Loaded) {
4645            my $ValueTypeObject = $Kernel::OM->Get(
4646                "Kernel::System::SysConfig::ValueType::$ValueType",
4647            );
4648
4649            my $ValueAttribute = $ValueTypeObject->ValueAttributeGet();
4650            if ( !grep { $_ eq $ValueAttribute } @Result ) {
4651                push @Result, $ValueAttribute;
4652            }
4653        }
4654    }
4655
4656    $CacheObject->Set(
4657        Type  => $CacheType,
4658        Key   => $CacheKey,
4659        Value => \@Result,
4660        TTL   => 24 * 3600,    # 1 day
4661    );
4662
4663    return @Result;
4664}
4665
4666=head2 SettingsSet()
4667
4668This method locks provided settings(by force), updates them and deploys the changes (by force).
4669
4670    my $Success = $SysConfigObject->SettingsSet(
4671        UserID   => 1,                                      # (required) UserID
4672        Comments => 'Deployment comment',                   # (optional) Comment
4673        Settings => [                                       # (required) List of settings to update.
4674            {
4675                Name                   => 'Setting::Name',  # (required)
4676                EffectiveValue         => 'Value',          # (optional)
4677                IsValid                => 1,                # (optional)
4678                UserModificationActive => 1,                # (optional)
4679            },
4680            ...
4681        ],
4682    );
4683
4684Returns:
4685
4686    $Success = 1;
4687
4688=cut
4689
4690sub SettingsSet {
4691    my ( $Self, %Param ) = @_;
4692
4693    if ( !$Param{UserID} ) {
4694        $Kernel::OM->Get('Kernel::System::Log')->Log(
4695            Priority => 'error',
4696            Message  => "Needed UserID!"
4697        );
4698
4699        return;
4700    }
4701
4702    if ( !IsArrayRefWithData( $Param{Settings} ) ) {
4703        $Kernel::OM->Get('Kernel::System::Log')->Log(
4704            Priority => 'error',
4705            Message  => "Settings must be array with data!"
4706        );
4707
4708        return;
4709    }
4710
4711    my @DeploySettings;
4712
4713    for my $Setting ( @{ $Param{Settings} } ) {
4714
4715        my $ExclusiveLockGUID = $Self->SettingLock(
4716            Name   => $Setting->{Name},
4717            Force  => 1,
4718            UserID => $Param{UserID},
4719        );
4720
4721        return if !$ExclusiveLockGUID;
4722
4723        my %UpdateResult = $Self->SettingUpdate(
4724            %{$Setting},
4725            ExclusiveLockGUID => $ExclusiveLockGUID,
4726            UserID            => $Param{UserID},
4727        );
4728
4729        if ( $UpdateResult{Success} ) {
4730            push @DeploySettings, $Setting->{Name};
4731        }
4732    }
4733
4734    # Deploy successfully updated settings.
4735    my %DeploymentResult = $Self->ConfigurationDeploy(
4736        Comments      => $Param{Comments} || '',
4737        UserID        => $Param{UserID},
4738        Force         => 1,
4739        DirtySettings => \@DeploySettings,
4740    );
4741
4742    return $DeploymentResult{Success};
4743}
4744
4745=head2 OverriddenFileNameGet()
4746
4747Returns file name which overrides setting Effective value.
4748
4749    my $FileName = $SysConfigObject->OverriddenFileNameGet(
4750        SettingName    => 'Setting::Name',  # (required)
4751        UserID         => 1,                # (required)
4752        EffectiveValue => '3',              # (optional) EffectiveValue stored in the DB.
4753    );
4754
4755Returns:
4756
4757    $FileName = 'ZZZUpdate.pm';
4758
4759=cut
4760
4761sub OverriddenFileNameGet {
4762    my ( $Self, %Param ) = @_;
4763
4764    # Check needed stuff.
4765    for my $Needed (qw(SettingName UserID)) {
4766        if ( !$Param{$Needed} ) {
4767            $Kernel::OM->Get('Kernel::System::Log')->Log(
4768                Priority => 'error',
4769                Message  => "Need $Needed!",
4770            );
4771            return;
4772        }
4773    }
4774
4775    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
4776
4777    my $LoadedEffectiveValue = $Self->GlobalEffectiveValueGet(
4778        SettingName => $Param{SettingName},
4779    );
4780    my @SettingStructure = split( '###', $Param{SettingName} );
4781
4782    my $EffectiveValue = $Param{EffectiveValue};
4783
4784    # Replace config variables in effective values.
4785    # NOTE: First level only, make sure to update this code once same mechanism has been improved in Defaults.pm.
4786    #   Please see bug#12916 and bug#13376 for more information.
4787    $EffectiveValue =~ s/\<OTRS_CONFIG_(.+?)\>/$ConfigObject->{$1}/g;
4788
4789    my $IsOverridden = DataIsDifferent(
4790        Data1 => $EffectiveValue       // {},
4791        Data2 => $LoadedEffectiveValue // {},
4792    );
4793
4794    # This setting is not Overridden in perl file, return.
4795    return if !$IsOverridden;
4796
4797    my $Result;
4798
4799    my $Home      = $ConfigObject->Get('Home');
4800    my $Directory = "$Home/Kernel/Config/Files";
4801
4802    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
4803
4804    # Get all .pm files that start with 'ZZZ'.
4805    my @FilesInDirectory = $MainObject->DirectoryRead(
4806        Directory => $Directory,
4807        Filter    => 'ZZZ*.pm',
4808    );
4809
4810    my @Modules;
4811
4812    FILE:
4813    for my $File (@FilesInDirectory) {
4814
4815        # Get only file name, without full path and extension.
4816        $File =~ m{^.*/(.*?)\.pm$};
4817        my $FileName = $1;
4818
4819        # Skip the file that was regularly deployed.
4820        next FILE if $FileName eq 'ZZZAAuto';
4821
4822        push @Modules, {
4823            "Kernel::Config::Files::$FileName" => $File,
4824        };
4825    }
4826
4827    # Check Config.pm as well.
4828    push @Modules, {
4829        'Kernel::Config' => 'Kernel/Config.pm',
4830    };
4831
4832    # Get effective values. Try cached version first.
4833    my $ConfigFromDB = {};
4834    if ( $Self->{ConfigFromDB} ) {
4835        $ConfigFromDB = $Self->{ConfigFromDB};
4836    }
4837
4838    # Check if we have a valid ZZZAAuto.pm. It is regarded as a reliable source of information.
4839    elsif (
4840        -f $Self->{Home} . '/Kernel/Config/Files/ZZZAAuto.pm'
4841        && $MainObject->Require('Kernel::Config::Files::ZZZAAuto')
4842        )
4843    {
4844        Kernel::Config::Files::ZZZAAuto->Load($ConfigFromDB);
4845
4846        return if !$ConfigFromDB;
4847        $Self->{ConfigFromDB} = $ConfigFromDB;
4848    }
4849
4850    # Try retrieving data from DB.
4851    elsif ( my %LastDeployment = $Kernel::OM->Get('Kernel::System::SysConfig::DB')->DeploymentGetLast() ) {
4852        return if !$LastDeployment{EffectiveValueStrg};
4853
4854        {
4855            eval $LastDeployment{EffectiveValueStrg};
4856            Kernel::Config::Files::ZZZAAuto->Load($ConfigFromDB);
4857        }
4858
4859        return if !$ConfigFromDB;
4860        $Self->{ConfigFromDB} = $ConfigFromDB;
4861    }
4862
4863    # No usable data found. This should only happen during initial setup before the initial deployment.
4864    else {
4865        return;
4866    }
4867
4868    for my $Module (@Modules) {
4869        my $ModuleName = ( keys %{$Module} )[0];
4870
4871        # Check if this module overrides our setting.
4872        my $SettingFound = $Self->_IsOverriddenInModule(
4873            Module           => $ModuleName,
4874            SettingStructure => \@SettingStructure,
4875            ConfigFromDB     => $ConfigFromDB,
4876        );
4877
4878        if ($SettingFound) {
4879            $Result = $Module->{$ModuleName};
4880        }
4881    }
4882
4883    if ($Result) {
4884        $Result =~ s/^$Home\/?(.*)$/$1/;
4885    }
4886
4887    return $Result;
4888}
4889
4890=head2 GlobalEffectiveValueGet()
4891
4892Returns global effective value for provided setting name.
4893
4894    my $EffectiveValue = $SysConfigObject->GlobalEffectiveValueGet(
4895        SettingName    => 'Setting::Name',  # (required)
4896    );
4897
4898Returns:
4899
4900    $EffectiveValue = 'test';
4901
4902=cut
4903
4904sub GlobalEffectiveValueGet {
4905    my ( $Self, %Param ) = @_;
4906
4907    # Check needed stuff.
4908    if ( !$Param{SettingName} ) {
4909        $Kernel::OM->Get('Kernel::System::Log')->Log(
4910            Priority => 'error',
4911            Message  => "Need SettingName!",
4912        );
4913        return;
4914    }
4915
4916    my $GlobalConfigObject = Kernel::Config->new();
4917
4918    my $LoadedEffectiveValue;
4919
4920    my @SettingStructure = split( '###', $Param{SettingName} );
4921    for my $Key (@SettingStructure) {
4922        if ( !defined $LoadedEffectiveValue ) {
4923
4924            # first iteration
4925            $LoadedEffectiveValue = $GlobalConfigObject->Get($Key);
4926        }
4927        elsif ( ref $LoadedEffectiveValue eq 'HASH' ) {
4928            $LoadedEffectiveValue = $LoadedEffectiveValue->{$Key};
4929        }
4930    }
4931
4932    return $LoadedEffectiveValue;
4933}
4934
4935=head1 PRIVATE INTERFACE
4936
4937=head2 _IsOverriddenInModule()
4938
4939Helper method to check if setting is overridden in specific module.
4940
4941    my $Overridden = $SysConfigObject->_IsOverriddenInModule(
4942        Module               => "Kernel::Config::Files::ZZZAAuto",
4943        SettingStructure     => [ 'DashboardBackend', '0000-ProductNotify' ],
4944        LoadedEffectiveValue => 'Value',
4945    );
4946
4947=cut
4948
4949sub _IsOverriddenInModule {
4950    my ( $Self, %Param ) = @_;
4951
4952    # Check needed stuff.
4953    for my $Needed (qw(Module SettingStructure ConfigFromDB)) {
4954        if ( !$Param{$Needed} ) {
4955            $Kernel::OM->Get('Kernel::System::Log')->Log(
4956                Priority => 'error',
4957                Message  => "Need $Needed!",
4958            );
4959            return;
4960        }
4961    }
4962
4963    if ( !IsArrayRefWithData( $Param{SettingStructure} ) ) {
4964        $Kernel::OM->Get('Kernel::System::Log')->Log(
4965            Priority => 'error',
4966            Message  => "SettingStructure must be an array!"
4967        );
4968        return;
4969    }
4970
4971    my $Result;
4972
4973    my $Loaded = $Kernel::OM->Get('Kernel::System::Main')->Require(
4974        $Param{Module},
4975        Silent => 1,
4976    );
4977
4978    # If module couldn't be loaded, there is no user specific setting.
4979    return $Result if !$Loaded;
4980
4981    # Get effective value from the DB.
4982    my $OverriddenSettings = $Kernel::OM->Get('Kernel::System::Storable')->Clone(
4983        Data => $Param{ConfigFromDB},
4984    );
4985
4986    if ( $Param{Module} eq 'Kernel::Config' ) {
4987        bless( $OverriddenSettings, 'Kernel::Config' );
4988        $OverriddenSettings->Load();
4989    }
4990    else {
4991        # Apply changes from this file only.
4992        $Param{Module}->Load($OverriddenSettings);
4993    }
4994
4995    # OverriddenSettings contains EffectiveValues from DB, overridden by provided Module,
4996    # so we can compare if setting was changed in this file.
4997
4998    # Loaded hash is empty, return.
4999    return $Result if !IsHashRefWithData($OverriddenSettings) && ref $OverriddenSettings ne 'Kernel::Config';
5000
5001    # Check if this file overrides our setting.
5002    my $SettingFound = 0;
5003    my $LoadedEffectiveValue;
5004    my $ConfigFromDB;
5005
5006    KEY:
5007    for my $Key ( @{ $Param{SettingStructure} } ) {
5008        if ( !defined $LoadedEffectiveValue ) {
5009
5010            # First iteration.
5011            $LoadedEffectiveValue = $OverriddenSettings->{$Key};
5012            $ConfigFromDB         = $Param{ConfigFromDB}->{$Key};
5013
5014            if ( defined $ConfigFromDB && !defined $LoadedEffectiveValue ) {
5015
5016                # Setting is overridden using the "delete" statement.
5017                $SettingFound = 1;
5018            }
5019            elsif (
5020                DataIsDifferent(
5021                    Data1 => $LoadedEffectiveValue // {},
5022                    Data2 => $ConfigFromDB         // {},
5023                )
5024                )
5025            {
5026                $SettingFound = 1;
5027            }
5028            else {
5029                last KEY;
5030            }
5031        }
5032        elsif ( ref $LoadedEffectiveValue eq 'HASH' ) {
5033            $LoadedEffectiveValue = $LoadedEffectiveValue->{$Key};
5034            $ConfigFromDB         = $ConfigFromDB->{$Key};
5035
5036            if ( defined $ConfigFromDB && !defined $LoadedEffectiveValue ) {
5037
5038                # Setting is overridden using the "delete" statement.
5039                $SettingFound = 1;
5040            }
5041            elsif (
5042                DataIsDifferent(
5043                    Data1 => $LoadedEffectiveValue // {},
5044                    Data2 => $ConfigFromDB         // {},
5045                )
5046                )
5047            {
5048                $SettingFound = 1;
5049            }
5050            else {
5051                $SettingFound = 0;
5052            }
5053        }
5054        else {
5055            $Kernel::OM->Get('Kernel::System::Log')->Log(
5056                Priority => 'error',
5057                Message  => "Unhandled exception!"
5058            );
5059        }
5060    }
5061
5062    return $SettingFound;
5063}
5064
5065=head2 _FileWriteAtomic()
5066
5067Writes a file in an atomic operation. This is achieved by creating
5068a temporary file, filling and renaming it. This avoids inconsistent states
5069when the file is updated.
5070
5071    my $Success = $SysConfigObject->_FileWriteAtomic(
5072        Filename => "$Self->{Home}/Kernel/Config/Files/ZZZAAuto.pm",
5073        Content  => \$NewContent,
5074    );
5075
5076=cut
5077
5078sub _FileWriteAtomic {
5079    my ( $Self, %Param ) = @_;
5080
5081    for my $Needed (qw(Filename Content)) {
5082        if ( !defined $Param{$Needed} ) {
5083            $Kernel::OM->Get('Kernel::System::Log')->Log(
5084                Priority => 'error',
5085                Message  => "Need $Needed!"
5086            );
5087            return;
5088        }
5089    }
5090
5091    my $TempFilename = $Param{Filename} . '.' . $$;
5092    my $FH;
5093
5094    ## no critic
5095    if ( !open( $FH, ">$Self->{FileMode}", $TempFilename ) ) {
5096        ## use critic
5097
5098        $Kernel::OM->Get('Kernel::System::Log')->Log(
5099            Priority => 'error',
5100            Message  => "Can't open file $TempFilename: $!",
5101        );
5102        return;
5103    }
5104
5105    print $FH ${ $Param{Content} };
5106    close $FH;
5107
5108    if ( !rename $TempFilename, $Param{Filename} ) {
5109        $Kernel::OM->Get('Kernel::System::Log')->Log(
5110            Priority => 'error',
5111            Message  => "Could not rename $TempFilename to $Param{Filename}: $!"
5112        );
5113        return;
5114    }
5115
5116    return 1;
5117}
5118
5119=head2 _ConfigurationTranslatableStrings()
5120
5121Gathers strings marked as translatable from a setting XML parsed content and saves it on
5122ConfigurationTranslatableStrings global variable.
5123
5124    $SysConfigObject->_ConfigurationTranslatableStrings(
5125        Data => $Data,      # could be SCALAR, ARRAY or HASH
5126    );
5127
5128=cut
5129
5130sub _ConfigurationTranslatableStrings {
5131    my ( $Self, %Param ) = @_;
5132
5133    for my $Needed (qw(Data)) {
5134        if ( !defined $Param{$Needed} ) {
5135            $Kernel::OM->Get('Kernel::System::Log')->Log(
5136                Priority => 'error',
5137                Message  => "Need $Needed!"
5138            );
5139            return;
5140        }
5141    }
5142
5143    # Start recursion if its an array.
5144    if ( ref $Param{Data} eq 'ARRAY' ) {
5145
5146        KEY:
5147        for my $Key ( @{ $Param{Data} } ) {
5148            next KEY if !$Key;
5149            $Self->_ConfigurationTranslatableStrings( Data => $Key );
5150        }
5151        return;
5152    }
5153
5154    # Start recursion if its a Hash.
5155    if ( ref $Param{Data} eq 'HASH' ) {
5156        for my $Key ( sort keys %{ $Param{Data} } ) {
5157            if (
5158                ref $Param{Data}->{$Key} eq ''
5159                && $Param{Data}->{Translatable}
5160                && $Param{Data}->{Content}
5161                )
5162            {
5163                return if !$Param{Data}->{Content};
5164                return if $Param{Data}->{Content} =~ /^\d+$/;
5165                $Self->{ConfigurationTranslatableStrings}->{ $Param{Data}->{Content} } = 1;
5166            }
5167            $Self->_ConfigurationTranslatableStrings( Data => $Param{Data}->{$Key} );
5168        }
5169    }
5170    return;
5171}
5172
5173=head2 _DBCleanUp();
5174
5175Removes all settings defined in the database (including default and modified) that are not included
5176in the settings parameter
5177
5178    my $Success = $SysConfigObject->_DBCleanUp(
5179        Settings => {
5180            'ACL::CacheTTL' => {
5181                XMLContentParsed => '
5182                    <Setting Name="SettingName" Required="1" Valid="1">
5183                        <Description Translatable="1">Test.</Description>
5184                        # ...
5185                    </Setting>',
5186                XMLContentRaw => {
5187                    Description => [
5188                        {
5189                            Content      => 'Test.',
5190                            Translatable => '1',
5191                        },
5192                    ],
5193                    Name  => 'Test',
5194                    # ...
5195                },
5196            # ...
5197        };
5198    );
5199
5200Returns:
5201
5202    $Success = 1;       # or false in case of a failure
5203
5204=cut
5205
5206sub _DBCleanUp {
5207    my ( $Self, %Param ) = @_;
5208
5209    for my $Needed (qw(Settings)) {
5210        if ( !$Param{$Needed} ) {
5211            $Kernel::OM->Get('Kernel::System::Log')->Log(
5212                Priority => 'error',
5213                Message  => "Need $Needed!"
5214            );
5215            return;
5216        }
5217    }
5218    if ( !IsHashRefWithData( $Param{Settings} ) ) {
5219        $Kernel::OM->Get('Kernel::System::Log')->Log(
5220            Priority => 'error',
5221            Message  => "Settings must be an HashRef!"
5222        );
5223        return;
5224    }
5225
5226    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
5227
5228    my @SettingsDB = $SysConfigDBObject->DefaultSettingList(
5229        IncludeInvisible => 1,
5230    );
5231
5232    my ( $DefaultUpdated, $ModifiedUpdated );
5233
5234    for my $SettingDB (@SettingsDB) {
5235
5236        # Cleanup database if the setting is not present in the XML files.
5237        if ( !$Param{Settings}->{ $SettingDB->{Name} } ) {
5238
5239            # Get all modified settings.
5240            my @ModifiedSettings = $SysConfigDBObject->ModifiedSettingListGet(
5241                Name => $SettingDB->{Name},
5242            );
5243
5244            for my $ModifiedSetting (@ModifiedSettings) {
5245
5246                # Delete from modified table.
5247                my $SuccessDeleteModified = $SysConfigDBObject->ModifiedSettingDelete(
5248                    ModifiedID => $ModifiedSetting->{ModifiedID},
5249                );
5250                if ( !$SuccessDeleteModified ) {
5251                    $Kernel::OM->Get('Kernel::System::Log')->Log(
5252                        Priority => 'error',
5253                        Message  => "System couldn't delete $SettingDB->{Name} from DB (sysconfig_modified)!"
5254                    );
5255                }
5256            }
5257
5258            my @ModifiedSettingVersions = $SysConfigDBObject->ModifiedSettingVersionListGet(
5259                Name => $SettingDB->{Name},
5260            );
5261
5262            for my $ModifiedSettingVersion (@ModifiedSettingVersions) {
5263
5264                # Delete from modified table.
5265                my $SuccessDeleteModifiedVersion = $SysConfigDBObject->ModifiedSettingVersionDelete(
5266                    ModifiedVersionID => $ModifiedSettingVersion->{ModifiedVersionID},
5267                );
5268                if ( !$SuccessDeleteModifiedVersion ) {
5269                    $Kernel::OM->Get('Kernel::System::Log')->Log(
5270                        Priority => 'error',
5271                        Message  => "System couldn't delete $SettingDB->{Name} from DB (sysconfig_modified_version)!"
5272                    );
5273                }
5274            }
5275
5276            # Delete from default table.
5277            my $SuccessDefaultSetting = $SysConfigDBObject->DefaultSettingDelete(
5278                Name => $SettingDB->{Name},
5279            );
5280            if ( !$SuccessDefaultSetting ) {
5281                $Kernel::OM->Get('Kernel::System::Log')->Log(
5282                    Priority => 'error',
5283                    Message  => "System couldn't delete $SettingDB->{Name} from DB (sysconfig_default)!"
5284                );
5285            }
5286        }
5287    }
5288
5289    return 1;
5290}
5291
5292=head2 _NavigationTree();
5293
5294Returns navigation as a tree (in a hash).
5295
5296    my %Result = $SysConfigObject->_NavigationTree(
5297        'Array' => [                            # Array of setting navigation items
5298            'Core',
5299            'Core::CustomerUser',
5300            'Frontend',
5301        ],
5302        'Tree' => {                             # Result from previous recursive call
5303            'Core' => {
5304                'Core::CustomerUser' => {},
5305            },
5306        },
5307    );
5308
5309Returns:
5310
5311    %Result = (
5312        'Core' => {
5313            'Core::CustomerUser' => {},
5314        },
5315        'Frontend' => {},
5316    );
5317
5318=cut
5319
5320sub _NavigationTree {
5321    my ( $Self, %Param ) = @_;
5322
5323    for my $Needed (qw(Tree Array)) {
5324        if ( !defined $Param{$Needed} ) {
5325            $Kernel::OM->Get('Kernel::System::Log')->Log(
5326                Priority => 'error',
5327                Message  => "Need $Needed!",
5328            );
5329            return;
5330        }
5331    }
5332
5333    my %Result = %{ $Param{Tree} };
5334
5335    return %Result if !IsArrayRefWithData( $Param{Array} );
5336
5337    # Check if first item exists.
5338    if ( !defined $Result{ $Param{Array}->[0] } ) {
5339        $Result{ $Param{Array}->[0] } = {
5340            Subitems => {},
5341        };
5342    }
5343
5344    # Check if it's deeper tree.
5345    if ( scalar @{ $Param{Array} } > 1 ) {
5346        my @SubArray = splice( @{ $Param{Array} }, 1 );
5347        my %Hash     = $Self->_NavigationTree(
5348            Tree  => $Result{ $Param{Array}->[0] }->{Subitems},
5349            Array => \@SubArray,
5350        );
5351
5352        if (%Hash) {
5353            $Result{ $Param{Array}->[0] } = {
5354                Subitems => \%Hash,
5355            };
5356        }
5357    }
5358
5359    return %Result;
5360}
5361
5362sub _NavigationTreeNodeCount {
5363    my ( $Self, %Param ) = @_;
5364
5365    # Check needed stuff.
5366    for my $Needed (qw(Settings)) {
5367        if ( !$Param{$Needed} ) {
5368            $Kernel::OM->Get('Kernel::System::Log')->Log(
5369                Priority => 'error',
5370                Message  => "Need $Needed!",
5371            );
5372            return;
5373        }
5374    }
5375
5376    my %Result = %{ $Param{Tree} // {} };
5377
5378    NODE_NAME:
5379    for my $NodeName ( sort keys %Result ) {
5380
5381        my @Matches = grep { $_->{Navigation} eq $NodeName } @{ $Param{Settings} };
5382        $Result{$NodeName}->{Count} = scalar @Matches;
5383
5384        my %SubResult = $Self->_NavigationTreeNodeCount(
5385            Tree     => $Result{$NodeName}->{Subitems},
5386            Settings => $Param{Settings},
5387        );
5388
5389        $Result{$NodeName}->{Subitems} = {
5390            %{ $Result{$NodeName}->{Subitems} },
5391            %SubResult,
5392        };
5393    }
5394
5395    return %Result;
5396}
5397
5398=head2 _ConfigurationEntitiesGet();
5399
5400Returns hash of used entities for provided Setting value.
5401
5402    my %Result = $SysConfigObject->_ConfigurationEntitiesGet(
5403        'Name'   => 'Ticket::Frontend::AgentTicketPriority###Entity',   # setting name
5404        'Result' => {},                                                 # result from previous recursive call
5405        'Value'  => [                                                   # setting Value
5406            {
5407                'Item' => [
5408                    {
5409                        'Content'         => '3 medium',
5410                        'ValueEntityType' => 'Priority',
5411                        'ValueRegex'      => '',
5412                        'ValueType'       => 'Entity',
5413                    },
5414                ],
5415            },
5416        ],
5417    );
5418
5419Returns:
5420
5421    %Result = {
5422        'Priority' => {
5423            '3 medium' => [
5424                'Ticket::Frontend::AgentTicketPriority###Entity',
5425            ],
5426        },
5427    };
5428
5429=cut
5430
5431sub _ConfigurationEntitiesGet {
5432    my ( $Self, %Param ) = @_;
5433
5434    for my $Needed (qw(Value Result Name)) {
5435        if ( !$Param{$Needed} ) {
5436            $Kernel::OM->Get('Kernel::System::Log')->Log(
5437                Priority => 'error',
5438                Message  => "Need $Needed!",
5439            );
5440            return;
5441        }
5442    }
5443
5444    my %Result          = %{ $Param{Result} || {} };
5445    my $ValueEntityType = $Param{ValueEntityType} || '';
5446
5447    if ( ref $Param{Value} eq 'ARRAY' ) {
5448        for my $Item ( @{ $Param{Value} } ) {
5449            %Result = $Self->_ConfigurationEntitiesGet(
5450                %Param,
5451                Value  => $Item,
5452                Result => \%Result,
5453            );
5454        }
5455    }
5456    elsif ( ref $Param{Value} eq 'HASH' ) {
5457        if ( $Param{Value}->{ValueEntityType} ) {
5458            $ValueEntityType = $Param{Value}->{ValueEntityType};
5459        }
5460
5461        if ( $Param{Value}->{Content} ) {
5462
5463            # If there is no hash item, create new.
5464            if ( !defined $Result{$ValueEntityType} ) {
5465                $Result{$ValueEntityType} = {};
5466            }
5467
5468            # Extract value (without white space).
5469            my $Value = $Param{Value}->{Content};
5470            $Value =~ s{^\s*(.*?)\s*$}{$1}gsmx;
5471            $Value //= '';
5472
5473            # If there is no array, create
5474            if ( !IsArrayRefWithData( $Result{$ValueEntityType}->{$Value} ) ) {
5475                $Result{$ValueEntityType}->{$Value} = [];
5476            }
5477
5478            # Check if current config is not in the array.
5479            if ( !grep { $_ eq $Param{Name} } @{ $Result{$ValueEntityType}->{$Value} } ) {
5480                push @{ $Result{$ValueEntityType}->{$Value} }, $Param{Name};
5481            }
5482        }
5483        else {
5484            for my $Key (qw(Item Hash Array)) {
5485                if ( defined $Param{Value}->{$Key} ) {
5486
5487                    # Contains children
5488                    %Result = $Self->_ConfigurationEntitiesGet(
5489                        %Param,
5490                        ValueEntityType => $ValueEntityType,
5491                        Value           => $Param{Value}->{$Key},
5492                        Result          => \%Result,
5493                    );
5494                }
5495            }
5496        }
5497    }
5498
5499    return %Result;
5500}
5501
5502=head2 _EffectiveValues2PerlFile()
5503
5504Converts effective values from settings into a combined perl hash ready to write into a file.
5505
5506    my $FileString = $SysConfigObject->_EffectiveValues2PerlFile(
5507        Settings  => [
5508            {
5509                Name           => 'SettingName',
5510                IsValid        => 1,
5511                EffectiveValue => $ValueStructure,
5512            },
5513            {
5514                Name           => 'AnotherSettingName',
5515                IsValid        => 0,
5516                EffectiveValue => $AnotherValueStructure,
5517            },
5518            # ...
5519        ],
5520        TargetPath => 'Kernel/Config/Files/ZZZAAuto.pm',
5521    );
5522
5523=cut
5524
5525sub _EffectiveValues2PerlFile {
5526    my ( $Self, %Param ) = @_;
5527
5528    for my $Needed (qw(Settings TargetPath)) {
5529        if ( !$Param{$Needed} ) {
5530            $Kernel::OM->Get('Kernel::System::Log')->Log(
5531                Priority => 'error',
5532                Message  => "Need $Needed!",
5533            );
5534
5535            return;
5536        }
5537    }
5538    if ( !IsArrayRefWithData( $Param{Settings} ) ) {
5539        $Kernel::OM->Get('Kernel::System::Log')->Log(
5540            Priority => 'error',
5541            Message  => "Settings parameter is invalid!",
5542        );
5543
5544        return;
5545    }
5546
5547    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
5548
5549    my $PerlHashStrg;
5550
5551    my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
5552    my $CacheObject    = $Kernel::OM->Get('Kernel::System::Cache');
5553
5554    my $CacheType = 'SysConfigPersistent';
5555    my $CacheKey  = 'EffectiveValues2PerlFile';
5556
5557    my $Cache = $CacheObject->Get(
5558        Type => $CacheType,
5559        Key  => $CacheKey,
5560    ) // {};
5561
5562    my $DateTimeObject    = $Kernel::OM->Create('Kernel::System::DateTime');
5563    my $CurrentSystemTime = $DateTimeObject->ToEpoch();
5564
5565    $DateTimeObject->Add(
5566        Months => 1,
5567    );
5568    my $ExpireTime = $DateTimeObject->ToEpoch();
5569
5570    my $CacheDifferent;
5571
5572    # Delete all expired keys.
5573    my @ExpiredKeys = grep { $CurrentSystemTime > $Cache->{$_}->{ExpireTime} } keys %{$Cache};
5574    delete @{$Cache}{@ExpiredKeys};
5575
5576    # If there are expired keys, cache needs to be set to a new value.
5577    $CacheDifferent = scalar @ExpiredKeys ? 1 : 0;
5578
5579    # Convert all settings from DB format to perl file.
5580    for my $Setting ( @{ $Param{Settings} } ) {
5581
5582        my $Name = $Setting->{Name};
5583        $Name =~ s/\\/\\\\/g;
5584        $Name =~ s/'/\'/g;
5585        $Name =~ s/###/'}->{'/g;
5586
5587        if ( $Setting->{IsValid} ) {
5588
5589            my $EffectiveValue;
5590
5591            my $ValueString = $Setting->{EffectiveValue} // '';
5592            if ( ref $ValueString ) {
5593                my $String = $StorableObject->Serialize(
5594                    Data => $Setting->{EffectiveValue},
5595                );
5596                $ValueString = $MainObject->MD5sum(
5597                    String => \$String,
5598                );
5599            }
5600
5601            if (
5602                $Cache->{$ValueString}
5603                && $Cache->{$ValueString}->{Value}
5604                )
5605            {
5606                $EffectiveValue = $Cache->{$ValueString}->{Value};
5607            }
5608            else {
5609                $EffectiveValue = $MainObject->Dump( $Setting->{EffectiveValue} );
5610
5611                $Cache->{$ValueString} = {
5612                    Value      => $EffectiveValue,
5613                    ExpireTime => $ExpireTime,
5614                };
5615
5616                # Cache has been changed, it needs to be set.
5617                $CacheDifferent = 1;
5618            }
5619
5620            $EffectiveValue =~ s/\$VAR1 =//;
5621            $PerlHashStrg .= "\$Self->{'$Name'} = $EffectiveValue";
5622        }
5623        elsif ( eval( '$Self->{ConfigDefaultObject}->{\'' . $Name . '\'}' ) ) {
5624            $PerlHashStrg .= "delete \$Self->{'$Name'};\n";
5625        }
5626    }
5627
5628    if ($CacheDifferent) {
5629
5630        $CacheObject->Set(
5631            Type  => $CacheType,
5632            Key   => $CacheKey,
5633            Value => $Cache,
5634            TTL   => 20 * 24 * 60 * 60,
5635        );
5636    }
5637
5638    chomp $PerlHashStrg;
5639
5640    # Convert TargetPath to Package.
5641    my $TargetPath = $Param{TargetPath};
5642    $TargetPath =~ s{(.*)\.(?:.*)}{$1}msx;
5643    $TargetPath =~ s{ / }{::}msxg;
5644
5645    # Write default config file.
5646    my $FileStrg = <<"EOF";
5647# OTRS config file (automatically generated)
5648# VERSION:2.0
5649package $TargetPath;
5650use strict;
5651use warnings;
5652no warnings 'redefine'; ## no critic
5653EOF
5654
5655    if ( $Self->{utf8} ) {
5656        $FileStrg .= "use utf8;\n";
5657    }
5658
5659    $FileStrg .= <<"EOF";
5660sub Load {
5661    my (\$File, \$Self) = \@_;
5662$PerlHashStrg
5663    return;
5664}
56651;
5666EOF
5667
5668    return $FileStrg;
5669}
5670
5671=head2 _SettingEffectiveValueCheck()
5672
5673Recursive helper for SettingEffectiveValueCheck().
5674
5675    my %Result = $SysConfigObject->_SettingEffectiveValueCheck(
5676        EffectiveValue => 'open',                           # (optional) The EffectiveValue to be checked,
5677                                                            #   (could be also a complex structure).
5678        XMLContentParsed => {                               # (required) The XMLContentParsed value from Default Setting.
5679            Value => [
5680                {
5681                    'Item' => [
5682                        {
5683                            'Content' => "Scalar value",
5684                        },
5685                    ],
5686                },
5687            ],
5688        },
5689        NoValidation        => $Param{NoValidation},        # (optional), skip validation
5690        CurrentSystemTime   => 1507894796935,               # (optional) Use provided 1507894796935, otherwise calculate
5691        ExpireTime          => 1507894896935,               # (optional) Use provided ExpireTime for cache, otherwise calculate
5692        UserID              => 1,                           # (required) UserID
5693    );
5694
5695Returns:
5696
5697    %Result = (
5698        EffectiveValue => 'closed',    # Note that EffectiveValue can be changed.
5699        Success        => 1,           # or false in case of fail
5700        Error          => undef,       # or error string
5701    );
5702
5703=cut
5704
5705sub _SettingEffectiveValueCheck {
5706    my ( $Self, %Param ) = @_;
5707
5708    # Check needed stuff.
5709    for my $Needed (qw(XMLContentParsed UserID)) {
5710        if ( !$Param{$Needed} ) {
5711            $Kernel::OM->Get('Kernel::System::Log')->Log(
5712                Priority => 'error',
5713                Message  => "Need $Needed!",
5714            );
5715            return;
5716        }
5717    }
5718
5719    # So far everything is OK, we need to check deeper (recursive).
5720    my $StorableObject = $Kernel::OM->Get('Kernel::System::Storable');
5721
5722    my $Default = $StorableObject->Clone(
5723        Data => $Param{XMLContentParsed},
5724    );
5725
5726    my $EffectiveValue = $Param{EffectiveValue};
5727
5728    if ( ref $Param{EffectiveValue} ) {
5729        $EffectiveValue = $StorableObject->Clone(
5730            Data => $Param{EffectiveValue},
5731        );
5732    }
5733
5734    return $Self->SettingEffectiveValueCheck(
5735        XMLContentParsed  => $Default,
5736        EffectiveValue    => $EffectiveValue,
5737        NoValidation      => $Param{NoValidation},
5738        CurrentSystemTime => $Param{CurrentSystemTime},
5739        ExpireTime        => $Param{ExpireTime},
5740        UserID            => $Param{UserID},
5741    );
5742}
5743
5744=head2 _SettingEffectiveValueCheckCacheSet()
5745Sets cache for EffectiveValueCheck to the provided value.
5746
5747    $SysConfigObject->_SettingEffectiveValueCheckCacheSet(
5748        Value => {                              (required)
5749            Default180920170714165331 => {
5750                Success => 1,
5751            },
5752            ...
5753        },
5754        NoValidation => 0,                      (optional)
5755    );
5756
5757=cut
5758
5759sub _SettingEffectiveValueCheckCacheSet {
5760    my ( $Self, %Param ) = @_;
5761
5762    # Check needed stuff.
5763    for my $Needed (qw(Value)) {
5764        if ( !defined $Param{$Needed} ) {
5765            $Kernel::OM->Get('Kernel::System::Log')->Log(
5766                Priority => 'error',
5767                Message  => "Need $Needed!",
5768            );
5769            return;
5770        }
5771    }
5772
5773    my $CacheType = 'SysConfigPersistent';
5774    my $CacheKey  = "EffectiveValueCheck::$Param{NoValidation}";
5775
5776    return $Kernel::OM->Get('Kernel::System::Cache')->Set(
5777        Type  => $CacheType,
5778        Key   => $CacheKey,
5779        Value => $Param{Value},
5780        TTL   => 20 * 24 * 60 * 60,
5781    );
5782}
5783
5784=head2 _GetSettingsToDeploy()
5785
5786Returns the correct list of settings for a deployment taking the settings from different sources:
5787
5788    NotDirty:      fetch default settings plus already deployed modified settings.
5789    AllSettings:   fetch default settings plus all modified settings already deployed or not.
5790    DirtySettings: fetch default settings plus already deployed settings plus all not deployed settings in the list.
5791
5792    my @SettingList = $SysConfigObject->_GetSettingsToDeploy(
5793        NotDirty      => 1,                                         # optional - exclusive (1||0)
5794        All           => 1,                                         # optional - exclusive (1||0)
5795        DirtySettings => [ 'SettingName1', 'SettingName2' ],        # optional - exclusive
5796    );
5797
5798    @SettingList = (
5799        {
5800            DefaultID                => 123,
5801            Name                     => "ProductName",
5802            Description              => "Defines the name of the application ...",
5803            Navigation               => "ASimple::Path::Structure",
5804            IsInvisible              => 1,
5805            IsReadonly               => 0,
5806            IsRequired               => 1,
5807            IsValid                  => 1,
5808            HasConfigLevel           => 200,
5809            UserModificationPossible => 0,          # 1 or 0
5810            UserModificationActive   => 0,          # 1 or 0
5811            UserPreferencesGroup     => 'Advanced', # optional
5812            XMLContentRaw            => "The XML structure as it is on the config file",
5813            XMLContentParsed         => "XML parsed to Perl",
5814            EffectiveValue           => "Product 6",
5815            DefaultValue             => "Product 5",
5816            IsModified               => 1,       # 1 or 0
5817            IsDirty                  => 1,       # 1 or 0
5818            ExclusiveLockGUID        => 'A32CHARACTERLONGSTRINGFORLOCKING',
5819            ExclusiveLockUserID      => 1,
5820            ExclusiveLockExpiryTime  => '2016-05-29 11:09:04',
5821            CreateTime               => "2016-05-29 11:04:04",
5822            ChangeTime               => "2016-05-29 11:04:04",
5823        },
5824        {
5825            DefaultID => 321,
5826            Name      => 'FieldName',
5827            # ...
5828            CreateTime => '2010-09-11 10:08:00',
5829            ChangeTime => '2011-01-01 01:01:01',
5830        },
5831        # ...
5832    );
5833
5834=cut
5835
5836sub _GetSettingsToDeploy {
5837    my ( $Self, %Param ) = @_;
5838
5839    if ( !$Param{NotDirty} && !$Param{DirtySettings} ) {
5840        $Param{AllSettings} = 1;
5841    }
5842
5843    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
5844
5845    my @DefaultSettingsList = $SysConfigDBObject->DefaultSettingListGet(
5846        NoCache => $Param{NoCache},
5847    );
5848
5849    # Create a lookup table for the default settings (for easy adding modified).
5850    my %SettingsLookup = map { $_->{Name} => $_ } @DefaultSettingsList;
5851
5852    my @ModifiedSettingsList;
5853
5854    # Use if - else statement, as the gathering of the settings could be expensive.
5855    if ( $Param{NotDirty} ) {
5856        @ModifiedSettingsList = $SysConfigDBObject->ModifiedSettingVersionListGetLast();
5857    }
5858    else {
5859        @ModifiedSettingsList = $SysConfigDBObject->ModifiedSettingListGet(
5860            IsGlobal => 1,
5861        );
5862    }
5863
5864    if ( $Param{AllSettings} || $Param{NotDirty} ) {
5865
5866        # Create a lookup table for the modified settings (for easy merging with defaults).
5867        my %ModifiedSettingsLookup = map { $_->{Name} => $_ } @ModifiedSettingsList;
5868
5869        # Merge modified into defaults.
5870        KEY:
5871        for my $Key ( sort keys %SettingsLookup ) {
5872            next KEY if !$ModifiedSettingsLookup{$Key};
5873
5874            $SettingsLookup{$Key} = {
5875                %{ $SettingsLookup{$Key} },
5876                %{ $ModifiedSettingsLookup{$Key} },
5877            };
5878        }
5879
5880        my @Settings = map { $SettingsLookup{$_} } ( sort keys %SettingsLookup );
5881
5882        return @Settings;
5883    }
5884
5885    my %DirtySettingsLookup = map { $_ => 1 } @{ $Param{DirtySettings} };
5886
5887    SETTING:
5888    for my $Setting (@ModifiedSettingsList) {
5889
5890        my $SettingName = $Setting->{Name};
5891
5892        # Skip invalid settings (all modified needs to have a default).
5893        next SETTING if !$SettingsLookup{$SettingName};
5894
5895        # Remember modified.
5896        my %ModifiedSetting = %{$Setting};
5897
5898        # If setting is not in the given list, then do not use current value but last deployed.
5899        if ( $Setting->{IsDirty} && !$DirtySettingsLookup{$SettingName} ) {
5900            %ModifiedSetting = $SysConfigDBObject->ModifiedSettingVersionGetLast(
5901                Name => $Setting->{Name},
5902            );
5903
5904            # If there is not previous version then skip to keep the default intact.
5905            next SETTING if !%ModifiedSetting;
5906        }
5907
5908        $SettingsLookup{$SettingName} = {
5909            %{ $SettingsLookup{$SettingName} },
5910            %ModifiedSetting,
5911        };
5912    }
5913
5914    my @Settings = map { $SettingsLookup{$_} } ( sort keys %SettingsLookup );
5915
5916    return @Settings;
5917}
5918
5919=head2 _HandleSettingsToDeploy()
5920
5921Creates modified versions of dirty settings to deploy and removed the dirty flag.
5922
5923    NotDirty:      Removes dirty flag just for default settings
5924    AllSettings:   Create a version for all dirty settings and removed dirty flags for all default and modified settings
5925    DirtySettings: Create a version and remove dirty fag for the modified settings in the list, remove dirty flag for all default settings
5926
5927    my $Success = $SysConfigObject->_HandleSettingsToDeploy(
5928        NotDirty            => 1,                                         # optional - exclusive (1||0)
5929        AllSettings         => 1,                                         # optional - exclusive (1||0)
5930        DirtySettings       => [ 'SettingName1', 'SettingName2' ],        # optional - exclusive
5931        DeploymentTimeStamp => 2017-12-12 12:00:00'
5932        UserID              => 123,
5933    );
5934
5935Returns:
5936
5937    $Success = 1;       # or false in case of a failure
5938
5939=cut
5940
5941sub _HandleSettingsToDeploy {
5942    my ( $Self, %Param ) = @_;
5943
5944    for my $Needed (qw(UserID DeploymentTimeStamp)) {
5945        if ( !$Param{$Needed} ) {
5946            $Kernel::OM->Get('Kernel::System::Log')->Log(
5947                Priority => 'error',
5948                Message  => "Need $Needed",
5949            );
5950            return;
5951        }
5952    }
5953
5954    if ( !$Param{NotDirty} && !$Param{DirtySettings} ) {
5955        $Param{AllSettings} = 1;
5956    }
5957
5958    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
5959
5960    # Remove is dirty flag for default settings.
5961    my $DefaultCleanup = $SysConfigDBObject->DefaultSettingDirtyCleanUp(
5962        AllSettings => $Param{AllSettings},
5963    );
5964    if ( !$DefaultCleanup ) {
5965        $Kernel::OM->Get('Kernel::System::Log')->Log(
5966            Priority => 'error',
5967            Message  => "Could not remove IsDirty flag from default settings",
5968        );
5969    }
5970
5971    return 1 if $Param{NotDirty};
5972
5973    my %DirtySettingsLookup = map { $_ => 1 } @{ $Param{DirtySettings} // [] };
5974
5975    # Get all dirty modified settings.
5976    my @DirtyModifiedList = $SysConfigDBObject->ModifiedSettingListGet(
5977        IsGlobal => 1,
5978        IsDirty  => 1,
5979    );
5980
5981    my %VersionsAdded;
5982    my @ModifiedDeleted;
5983    my @ModifiedIDs;
5984    my $Error;
5985
5986    # Create a new version for the modified settings.
5987    SETTING:
5988    for my $Setting (@DirtyModifiedList) {
5989
5990        # Skip setting if it is not in the list (and it is not a full deployment)
5991        next SETTING if !$Param{AllSettings} && !$DirtySettingsLookup{ $Setting->{Name} };
5992
5993        my %DefaultSettingVersionGetLast = $SysConfigDBObject->DefaultSettingVersionGetLast(
5994            DefaultID => $Setting->{DefaultID},
5995        );
5996
5997        my $ModifiedVersionID = $SysConfigDBObject->ModifiedSettingVersionAdd(
5998            %{$Setting},
5999            DefaultVersionID    => $DefaultSettingVersionGetLast{DefaultVersionID},
6000            DeploymentTimeStamp => $Param{DeploymentTimeStamp},
6001            UserID              => $Param{UserID},
6002        );
6003
6004        if ( !$ModifiedVersionID ) {
6005            $Kernel::OM->Get('Kernel::System::Log')->Log(
6006                Priority => 'error',
6007                Message  => "Could not create a modified setting version for $Setting->{Name}! Rolling back.",
6008            );
6009            $Error = 1;
6010            last SETTING;
6011        }
6012
6013        $VersionsAdded{ $Setting->{Name} } = $ModifiedVersionID;
6014
6015        if ( !$Setting->{ResetToDefault} ) {
6016            push @ModifiedIDs, $Setting->{ModifiedID};
6017            next SETTING;
6018        }
6019
6020        # In case a setting value reset, delete the modified value.
6021        my $ModifiedDelete = $SysConfigDBObject->ModifiedSettingDelete(
6022            ModifiedID => $Setting->{ModifiedID},
6023        );
6024
6025        if ( !$ModifiedDelete ) {
6026            $Kernel::OM->Get('Kernel::System::Log')->Log(
6027                Priority => 'error',
6028                Message =>
6029                    "Could not delete the modified setting for $Setting->{Name} on reset action! Rolling back.",
6030            );
6031            $Error = 1;
6032            last SETTING;
6033        }
6034
6035        push @ModifiedDeleted, $Setting;
6036    }
6037
6038    # In case of an error:
6039    #   Remove "all" added versions for "all" settings for this deployment.
6040    #   Restore "all" deleted modified settings.
6041    if ($Error) {
6042        for my $SettingName ( sort keys %VersionsAdded ) {
6043            my $Success = $SysConfigDBObject->ModifiedSettingVersionDelete(
6044                ModifiedVersionID => $VersionsAdded{$SettingName},
6045            );
6046        }
6047
6048        for my $Setting (@ModifiedDeleted) {
6049            my $Success = $SysConfigDBObject->ModifiedSettingAdd(
6050                %{$Setting},
6051                DeploymentExclusiveLockGUID => $Param{DeploymentExclusiveLockGUID},
6052                UserID                      => $Setting->{ChangeBy},
6053            );
6054        }
6055
6056        return;
6057    }
6058
6059    # Do not clean dirty flag if no setting version was created and it is not a full deployment
6060    return 1 if !$Param{AllSettings} && !@ModifiedIDs;
6061
6062    my %Options;
6063    if ( !$Param{AllSettings} ) {
6064        $Options{ModifiedIDs} = \@ModifiedIDs;
6065    }
6066
6067    # Remove is dirty flag for modified settings.
6068    my $ModifiedCleanup = $SysConfigDBObject->ModifiedSettingDirtyCleanUp(%Options);
6069    if ( !$ModifiedCleanup ) {
6070        $Kernel::OM->Get('Kernel::System::Log')->Log(
6071            Priority => 'error',
6072            Message  => "Could not remove IsDirty flag from modified settings",
6073        );
6074    }
6075
6076    return 1;
6077}
6078
6079=head2 _SettingTranslatedGet()
6080
6081Helper method for ConfigurationTranslatedGet().
6082
6083    my %Result = $SysConfigObject->_SettingTranslatedGet(
6084        Language => 'de',               # (required) User language
6085        Name     => 'SettingName',      # (required) Setting name
6086        Silent   => 1,                  # (optional) Default 1
6087    );
6088
6089Returns:
6090
6091    %Result = (
6092       'ACL::CacheTTL' => {
6093            'Category' => 'OTRS',
6094            'IsInvisible' => '0',
6095            'Metadata' => "ACL::CacheTTL--- '3600'
6096Cache-Zeit in Sekunden f\x{fc}r Datenbank ACL-Backends.",
6097    );
6098
6099=cut
6100
6101sub _SettingTranslatedGet {
6102    my ( $Self, %Param ) = @_;
6103
6104    # Check needed stuff.
6105    for my $Needed (qw(Language Name)) {
6106        if ( !$Param{$Needed} ) {
6107            $Kernel::OM->Get('Kernel::System::Log')->Log(
6108                Priority => 'error',
6109                Message  => "Need $Needed!",
6110            );
6111            return;
6112        }
6113    }
6114
6115    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
6116
6117    my $CacheType = 'SysConfig';
6118    my $CacheKey  = "SettingTranslatedGet::$Param{Language}::$Param{Name}";
6119
6120    # Return cache.
6121    my $Cache = $CacheObject->Get(
6122        Type => $CacheType,
6123        Key  => $CacheKey,
6124    );
6125
6126    return %{$Cache} if ref $Cache eq 'HASH';
6127
6128    my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML');
6129    my %Categories = $Self->ConfigurationCategoriesGet();
6130
6131    my %SettingTranslated = $Self->SettingGet(
6132        Name      => $Param{Name},
6133        Translate => 1,
6134    );
6135
6136    my $Metadata = $Param{Name};
6137    $Metadata .= $YAMLObject->Dump(
6138        Data => $SettingTranslated{EffectiveValue},
6139    );
6140    $Metadata .= $SettingTranslated{Description};
6141
6142    my %Result;
6143    $Result{ $Param{Name} }->{Metadata} = lc $Metadata;
6144
6145    # Check setting category.
6146    my $SettingCategory;
6147
6148    my $Silent = $Param{Silent} // 1;
6149
6150    CATEGORY:
6151    for my $Category ( sort keys %Categories ) {
6152        if ( grep { $_ eq $SettingTranslated{XMLFilename} } @{ $Categories{$Category}->{Files} } ) {
6153            $SettingCategory = $Category;
6154            last CATEGORY;
6155        }
6156    }
6157
6158    if ( !$SettingCategory ) {
6159        if ( !$Silent ) {
6160            $Kernel::OM->Get('Kernel::System::Log')->Log(
6161                Priority => 'error',
6162                Message  => "Category couldn't be determined for $Param{Name}!",
6163            );
6164        }
6165        $SettingCategory = '-Unknown-';
6166    }
6167    $Result{ $Param{Name} }->{Category}    = $SettingCategory;
6168    $Result{ $Param{Name} }->{IsInvisible} = $SettingTranslated{IsInvisible};
6169
6170    $CacheObject->Set(
6171        Type  => $CacheType,
6172        Key   => $CacheKey,
6173        Value => \%Result,
6174        TTL   => $Self->{CacheTTL} || 24 * 60 * 60,
6175    );
6176
6177    return %Result;
6178}
6179
6180=head2 _ValueTypesList()
6181
6182Returns a hash of forbidden value types.
6183
6184    my @ValueTypes = $SysConfigObject->_ValueTypesList();
6185
6186Returns:
6187
6188    @ValueTypes = (
6189        "Checkbox",
6190        "Select",
6191        ...
6192    );
6193
6194=cut
6195
6196sub _ValueTypesList {
6197    my ( $Self, %Param ) = @_;
6198
6199    my $CacheType = 'SysConfig';
6200    my $CacheKey  = '_ValueTypesList';
6201
6202    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
6203
6204    # Return cache.
6205    my $Cache = $CacheObject->Get(
6206        Type => $CacheType,
6207        Key  => $CacheKey,
6208    );
6209
6210    return @{$Cache} if ref $Cache eq 'ARRAY';
6211
6212    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
6213
6214    my @Files = $MainObject->DirectoryRead(
6215        Directory => $Self->{Home} . "/Kernel/System/SysConfig/ValueType",
6216        Filter    => '*.pm',
6217    );
6218
6219    my @Result;
6220    for my $File (@Files) {
6221
6222        my $ValueType = $File;
6223
6224        # Remove folder path.
6225        $ValueType =~ s{^.*/}{}sm;
6226
6227        # Remove extension
6228        $ValueType =~ s{\.pm$}{}sm;
6229
6230        push @Result, $ValueType;
6231    }
6232
6233    $CacheObject->Set(
6234        Type  => $CacheType,
6235        Key   => $CacheKey,
6236        Value => \@Result,
6237        TTL   => 24 * 3600,    # 1 day
6238    );
6239
6240    return @Result;
6241}
6242
6243=head2 _DefaultSettingAddBulk()
6244
6245Helper method for ConfigurationXML2DB() - bulk insert.
6246
6247    my $Success = $SysConfigObject->_DefaultSettingAddBulk(
6248        Settings => {                   # (required) Hash of settings to insert
6249            'SettingName' => {
6250
6251            },
6252            ...
6253        },
6254        SettingList => [                # (required) List of settings
6255            ...
6256        ],
6257        UserID => 1,                    # (required) UserID
6258    );
6259
6260=cut
6261
6262sub _DefaultSettingAddBulk {
6263    my ( $Self, %Param ) = @_;
6264
6265    # Check needed stuff.
6266    for my $Needed (qw(Settings SettingList UserID)) {
6267        if ( !$Param{$Needed} ) {
6268            $Kernel::OM->Get('Kernel::System::Log')->Log(
6269                Priority => 'error',
6270                Message  => "Need $Needed!",
6271            );
6272            return;
6273        }
6274    }
6275
6276    # Check needed stuff.
6277    if ( ref $Param{Settings} ne 'HASH' ) {
6278        $Kernel::OM->Get('Kernel::System::Log')->Log(
6279            Priority => 'error',
6280            Message  => "Settings must be a HASH ref!",
6281        );
6282        return;
6283    }
6284
6285    my $StorableObject    = $Kernel::OM->Get('Kernel::System::Storable');
6286    my $SysConfigDBObject = $Kernel::OM->Get('Kernel::System::SysConfig::DB');
6287    my $YAMLObject        = $Kernel::OM->Get('Kernel::System::YAML');
6288
6289    my %Settings    = %{ $Param{Settings} };
6290    my @SettingList = @{ $Param{SettingList} };
6291
6292    for my $SettingName ( sort keys %{ $Param{Settings} } ) {
6293
6294        # Create a local clone of the value to prevent any modification.
6295        my $Value = $StorableObject->Clone(
6296            Data => $Settings{$SettingName}->{XMLContentParsed}->{Value},
6297        );
6298
6299        $Settings{$SettingName}->{EffectiveValue} = $Self->SettingEffectiveValueGet(
6300            Value => $Value,
6301        );
6302
6303        # Serialize values that doesn't have string representation.
6304        $Settings{$SettingName}->{EffectiveValue} = $YAMLObject->Dump(
6305            Data => $Settings{$SettingName}->{EffectiveValue},
6306        );
6307        $Settings{$SettingName}->{XMLContentParsedYAML} = $YAMLObject->Dump(
6308            Data => $Settings{$SettingName}->{XMLContentParsed},
6309        );
6310    }
6311
6312    my $Success = $SysConfigDBObject->DefaultSettingBulkAdd(
6313        Settings    => \%Settings,
6314        SettingList => \@SettingList,
6315        UserID      => $Param{UserID},
6316    );
6317
6318    if ( !$Success ) {
6319        $Kernel::OM->Get('Kernel::System::Log')->Log(
6320            Priority => 'error',
6321            Message  => "System was unable to rebuild config!"
6322        );
6323        return;
6324    }
6325
6326    # Get again all settings.
6327    @SettingList = $Self->ConfigurationList(
6328        IncludeInvisible => 1,
6329    );
6330
6331    $Success = $SysConfigDBObject->DefaultSettingVersionBulkAdd(
6332        Settings    => \%Settings,
6333        SettingList => \@SettingList,
6334        UserID      => $Param{UserID},
6335    );
6336
6337    if ( !$Success ) {
6338        $Kernel::OM->Get('Kernel::System::Log')->Log(
6339            Priority => 'error',
6340            Message  => "System was unable to rebuild config!"
6341        );
6342        return;
6343    }
6344
6345    return 1;
6346}
6347
63481;
6349
6350=head1 TERMS AND CONDITIONS
6351
6352This software is part of the OTRS project (L<https://otrs.org/>).
6353
6354This software comes with ABSOLUTELY NO WARRANTY. For details, see
6355the enclosed file COPYING for license information (GPL). If you
6356did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
6357
6358=cut
6359