# --
# Copyright (C) 2001-2021 OTRS AG, https://otrs.com/
# Copyright (C) 2021 Znuny GmbH, https://znuny.org/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --

package Kernel::Modules::AdminACL;

use strict;
use warnings;

our $ObjectManagerDisabled = 1;

use Kernel::System::VariableCheck qw(:all);
use Kernel::Language qw(Translatable);

sub new {
    my ( $Type, %Param ) = @_;

    # allocate new hash for object
    my $Self = {%Param};
    bless( $Self, $Type );

    return $Self;
}

sub Run {
    my ( $Self, %Param ) = @_;

    my $ParamObject  = $Kernel::OM->Get('Kernel::System::Web::Request');
    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');

    $Self->{Subaction} = $ParamObject->GetParam( Param => 'Subaction' ) || '';

    my $ACLID = $ParamObject->GetParam( Param => 'ID' ) || '';

    my $SynchronizeMessage = Translatable(
        'ACL information from database is not in sync with the system configuration, please deploy all ACLs.'
    );

    my $SynchronizedMessageVisible = 0;

    my $ACLObject = $Kernel::OM->Get('Kernel::System::ACL::DB::ACL');

    if ( $ACLObject->ACLsNeedSync() ) {

        # create a notification if system is not up to date
        $Param{NotifyData} = [
            {
                Info => $SynchronizeMessage,
                Link => $LayoutObject->{Baselink} . 'Action=AdminACL;Subaction=ACLDeploy'
            },
        ];
        $SynchronizedMessageVisible = 1;
    }

    # ------------------------------------------------------------ #
    # ACLImport
    # ------------------------------------------------------------ #
    if ( $Self->{Subaction} eq 'ACLImport' ) {

        # challenge token check for write action
        $LayoutObject->ChallengeTokenCheck();

        my $FormID      = $ParamObject->GetParam( Param => 'FormID' ) || '';
        my %UploadStuff = $ParamObject->GetUploadAll(
            Param  => 'FileUpload',
            Source => 'string',
        );

        my $OverwriteExistingEntities = $ParamObject->GetParam( Param => 'OverwriteExistingEntities' ) || '';

        my $ACLImport = $ACLObject->ACLImport(
            Content                   => $UploadStuff{Content},
            OverwriteExistingEntities => $OverwriteExistingEntities,
            UserID                    => $Self->{UserID},
        );

        if ( !$ACLImport->{Success} ) {
            my $Message = $ACLImport->{Message}
                || Translatable(
                'ACLs could not be Imported due to a unknown error, please check Znuny logs for more information'
                );
            return $LayoutObject->ErrorScreen(
                Message => $Message,
            );
        }

        if ( $ACLImport->{AddedACLs} ) {
            push @{ $Param{NotifyData} }, {
                Info => $LayoutObject->{LanguageObject}->Translate(
                    'The following ACLs have been added successfully: %s',
                    $ACLImport->{AddedACLs}
                ),
            };
        }
        if ( $ACLImport->{UpdatedACLs} ) {
            push @{ $Param{NotifyData} }, {
                Info => $LayoutObject->{LanguageObject}->Translate(
                    'The following ACLs have been updated successfully: %s',
                    $ACLImport->{UpdatedACLs}
                ),
            };
        }
        if ( $ACLImport->{ACLErrors} ) {
            push @{ $Param{NotifyData} }, {
                Priority => 'Error',
                Info     => $LayoutObject->{LanguageObject}->Translate(
                    'There where errors adding/updating the following ACLs: %s. Please check the log file for more information.',
                    $ACLImport->{ACLErrors}
                ),
            };
        }

        if (
            ( $ACLImport->{UpdatedACLs} || $ACLImport->{AddedACLs} )
            && !$SynchronizedMessageVisible
            )
        {

            $Param{NotifyData} = [
                @{ $Param{NotifyData} || [] },
                {
                    Info => $SynchronizeMessage,
                },
            ];
        }

        return $Self->_ShowOverview(
            %Param,
        );
    }

    # ------------------------------------------------------------ #
    # ACLNew
    # ------------------------------------------------------------ #
    elsif ( $Self->{Subaction} eq 'ACLNew' ) {

        return $Self->_ShowEdit(
            %Param,
            Action => 'New',
        );
    }

    # ------------------------------------------------------------ #
    # ACLNewAction
    # ------------------------------------------------------------ #
    elsif ( $Self->{Subaction} eq 'ACLNewAction' ) {

        # challenge token check for write action
        $LayoutObject->ChallengeTokenCheck();

        # get ACL data
        my $ACLData;

        # get parameter from web browser
        my $GetParam = $Self->_GetParams();

        # set new confguration
        $ACLData->{Name}           = $GetParam->{Name};
        $ACLData->{Comment}        = $GetParam->{Comment};
        $ACLData->{Description}    = $GetParam->{Description};
        $ACLData->{StopAfterMatch} = $GetParam->{StopAfterMatch} || 0;
        $ACLData->{ValidID}        = $GetParam->{ValidID};

        # check required parameters
        my %Error;
        if ( !$GetParam->{Name} ) {

            # add server error error class
            $Error{NameServerError}        = 'ServerError';
            $Error{NameServerErrorMessage} = Translatable('This field is required');
        }

        if ( !$GetParam->{ValidID} ) {

            # add server error error class
            $Error{ValidIDServerError}        = 'ServerError';
            $Error{ValidIDServerErrorMessage} = Translatable('This field is required');
        }

        # if there is an error return to edit screen
        if ( IsHashRefWithData( \%Error ) ) {
            return $Self->_ShowEdit(
                %Error,
                %Param,
                ACLData => $ACLData,
                Action  => 'New',
            );
        }

        # otherwise save configuration and return to overview screen
        my $ACLID = $ACLObject->ACLAdd(
            Name           => $ACLData->{Name},
            Comment        => $ACLData->{Comment},
            Description    => $ACLData->{Description},
            StopAfterMatch => $ACLData->{StopAfterMatch},
            ValidID        => $ACLData->{ValidID},
            UserID         => $Self->{UserID},
        );

        # show error if can't create
        if ( !$ACLID ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable('There was an error creating the ACL'),
            );
        }

        # redirect to edit screen
        return $LayoutObject->Redirect( OP => "Action=$Self->{Action};Subaction=ACLEdit;ID=$ACLID" );
    }

    # ------------------------------------------------------------ #
    # ACLEdit
    # ------------------------------------------------------------ #
    elsif ( $Self->{Subaction} eq 'ACLEdit' ) {

        # check for ACLID
        if ( !$ACLID ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable('Need ACLID!'),
            );
        }

        # get ACL data
        my $ACLData = $ACLObject->ACLGet(
            ID     => $ACLID,
            UserID => $Self->{UserID},
        );

        # check for valid ACL data
        if ( !IsHashRefWithData($ACLData) ) {
            return $LayoutObject->ErrorScreen(
                Message => $LayoutObject->{LanguageObject}->Translate( 'Could not get data for ACLID %s', $ACLID ),
            );
        }

        my $JSONObject = $Kernel::OM->Get('Kernel::System::JSON');

        if ( $ACLData->{ConfigMatch} ) {
            $ACLData->{ConfigMatch} = $JSONObject->Encode(
                Data => $ACLData->{ConfigMatch},
            );
        }

        if ( $ACLData->{ConfigChange} ) {
            $ACLData->{ConfigChange} = $JSONObject->Encode(
                Data => $ACLData->{ConfigChange},
            );
        }

        return $Self->_ShowEdit(
            %Param,
            ACLID   => $ACLID,
            ACLData => $ACLData,
            Action  => 'Edit',
        );
    }

    # ------------------------------------------------------------ #
    # ACLEditAction
    # ------------------------------------------------------------ #
    elsif ( $Self->{Subaction} eq 'ACLEditAction' ) {

        # challenge token check for write action
        $LayoutObject->ChallengeTokenCheck();

        # get webserice configuration
        my $ACLData;

        # get parameter from web browser
        my $GetParam = $Self->_GetParams();

        # set new confguration
        $ACLData->{Name}           = $GetParam->{Name};
        $ACLData->{Comment}        = $GetParam->{Comment};
        $ACLData->{Description}    = $GetParam->{Description};
        $ACLData->{StopAfterMatch} = $GetParam->{StopAfterMatch} || 0;
        $ACLData->{ValidID}        = $GetParam->{ValidID};
        $ACLData->{ConfigMatch}    = $GetParam->{ConfigMatch} || '';
        $ACLData->{ConfigChange}   = $GetParam->{ConfigChange} || '';

        # check required parameters
        my %Error;
        if ( !$GetParam->{Name} ) {

            # add server error error class
            $Error{NameServerError}        = 'ServerError';
            $Error{NameServerErrorMessage} = Translatable('This field is required');
        }

        if ( !$GetParam->{ValidID} ) {

            # add server error error class
            $Error{ValidIDServerError}        = 'ServerError';
            $Error{ValidIDServerErrorMessage} = Translatable('This field is required');
        }

        # if there is an error return to edit screen
        if ( IsHashRefWithData( \%Error ) ) {
            return $Self->_ShowEdit(
                %Error,
                %Param,
                ACLData => $ACLData,
                Action  => 'Edit',
            );
        }

        # otherwise save configuration and return to overview screen
        my $Success = $ACLObject->ACLUpdate(
            ID             => $ACLID,
            Name           => $ACLData->{Name},
            Comment        => $ACLData->{Comment},
            Description    => $ACLData->{Description},
            StopAfterMatch => $ACLData->{StopAfterMatch} || 0,
            ValidID        => $ACLData->{ValidID},
            ConfigMatch    => $ACLData->{ConfigMatch} || '',
            ConfigChange   => $ACLData->{ConfigChange} || '',
            UserID         => $Self->{UserID},
        );

        # show error if can't update
        if ( !$Success ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable('There was an error updating the ACL'),
            );
        }

        if (
            defined $ParamObject->GetParam( Param => 'ContinueAfterSave' )
            && ( $ParamObject->GetParam( Param => 'ContinueAfterSave' ) eq '1' )
            )
        {

            # if the user would like to continue editing the ACL, just redirect to the edit screen
            return $LayoutObject->Redirect(
                OP =>
                    "Action=AdminACL;Subaction=ACLEdit;ID=$ACLID"
            );
        }
        else {

            # otherwise return to overview
            return $LayoutObject->Redirect( OP => "Action=$Self->{Action}" );
        }
    }

    # ------------------------------------------------------------ #
    # Sync
    # ------------------------------------------------------------ #
    elsif ( $Self->{Subaction} eq 'ACLDeploy' ) {

        my $Location = $Kernel::OM->Get('Kernel::Config')->Get('Home') . '/Kernel/Config/Files/ZZZACL.pm';

        my $ACLDump = $ACLObject->ACLDump(
            ResultType => 'FILE',
            Location   => $Location,
            UserID     => $Self->{UserID},
        );

        if ($ACLDump) {

            my $Success = $ACLObject->ACLsNeedSyncReset();

            if ($Success) {
                return $LayoutObject->Redirect( OP => "Action=$Self->{Action}" );
            }
            else {

                # show error if can't set state
                return $LayoutObject->ErrorScreen(
                    Message => Translatable('There was an error setting the entity sync status.'),
                );
            }
        }
        else {

            # show error if can't synch
            return $LayoutObject->ErrorScreen(
                Message => Translatable('There was an error synchronizing the ACLs.'),
            );
        }
    }

    # ------------------------------------------------------------ #
    # ACLDelete AJAX
    # ------------------------------------------------------------ #
    elsif ( $Self->{Subaction} eq 'ACLDelete' ) {

        # challenge token check for write action
        $LayoutObject->ChallengeTokenCheck();

        # check for ACLID
        return if !$ACLID;

        my $CheckResult = $Self->_CheckACLDelete( ID => $ACLID );

        my $JSON;
        if ( $CheckResult->{Success} ) {

            my $Success = $ACLObject->ACLDelete(
                ID     => $ACLID,
                UserID => $Self->{UserID},
            );

            my %DeleteResult = (
                Success => $Success,
            );

            if ( !$Success ) {
                $DeleteResult{Message}
                    = $LayoutObject->{LanguageObject}->Translate( 'ACL %s could not be deleted', $ACLID );
            }

            # build JSON output
            $JSON = $LayoutObject->JSONEncode(
                Data => {
                    %DeleteResult,
                },
            );
        }
        else {

            # build JSON output
            $JSON = $LayoutObject->JSONEncode(
                Data => {
                    %{$CheckResult},
                },
            );
        }

        # send JSON response
        return $LayoutObject->Attachment(
            ContentType => 'application/json; charset=' . $LayoutObject->{Charset},
            Content     => $JSON,
            Type        => 'inline',
            NoCache     => 1,
        );
    }

    # ------------------------------------------------------------ #
    # ACLExport
    # ------------------------------------------------------------ #
    elsif ( $Self->{Subaction} eq 'ACLExport' ) {

        my $ACLID = $ParamObject->GetParam( Param => 'ID' ) || '';
        my $ACLData;
        my $ACLSingleData;
        my $Filename = 'Export_ACL.yml';

        if ($ACLID) {

            $ACLSingleData = $ACLObject->ACLGet(
                ID     => $ACLID,
                UserID => 1,
            );

            if ( !$ACLSingleData || !IsHashRefWithData($ACLSingleData) ) {
                return $LayoutObject->ErrorScreen(
                    Message => $LayoutObject->{LanguageObject}
                        ->Translate( 'There was an error getting data for ACL with ID %s', $ACLID ),
                );
            }

            my $ACLName = $ACLSingleData->{Name};
            $ACLName =~ s{[^a-zA-Z0-9-_]}{_}xmsg;    # cleanup name for saving

            $Filename = 'Export_ACL_' . $ACLName . '.yml';
            $ACLData  = [$ACLSingleData];
        }
        else {

            # Get all IDs from valid table including invalid-temporarily status.
            # See bug#13592 (https://bugs.otrs.org/show_bug.cgi?id=13592).
            my %ValidList    = $Kernel::OM->Get('Kernel::System::Valid')->ValidList();
            my @ValidListIDs = grep { $ValidList{$_} } sort keys %ValidList;

            $ACLData = $ACLObject->ACLListGet(
                UserID   => 1,
                ValidIDs => \@ValidListIDs,
            );
        }

        # convert the ACL data hash to string
        my $ACLDataYAML = $Kernel::OM->Get('Kernel::System::YAML')->Dump( Data => $ACLData );

        # send the result to the browser
        return $LayoutObject->Attachment(
            ContentType => 'text/html; charset=' . $LayoutObject->{Charset},
            Content     => $ACLDataYAML,
            Type        => 'attachment',
            Filename    => $Filename,
            NoCache     => 1,
        );
    }

    # ------------------------------------------------------------ #
    # ACLCopy
    # ------------------------------------------------------------ #
    elsif ( $Self->{Subaction} eq 'ACLCopy' ) {

        # challenge token check for write action
        $LayoutObject->ChallengeTokenCheck();

        # get ACL data
        my $ACLData = $ACLObject->ACLGet(
            ID     => $ACLID,
            UserID => $Self->{UserID},
        );
        if ( !$ACLData ) {
            return $LayoutObject->ErrorScreen(
                Message => "Unknown ACL $ACLID!",
            );
        }

        # Create new ACL name.
        my $Count   = 1;
        my $ACLName = $LayoutObject->{LanguageObject}->Translate( '%s (copy) %s', $ACLData->{Name}, $Count );
        while (
            $ACLObject->ACLGet(
                Name   => $ACLName,
                UserID => $Self->{UserID}
            )
            && $Count < 100
            )
        {
            $ACLName =~ s/\d+$/$Count/;
            $Count++;
        }

        # otherwise save configuration and return to overview screen
        my $ACLID = $ACLObject->ACLAdd(
            Name           => $ACLName,
            Comment        => $ACLData->{Comment},
            Description    => $ACLData->{Description},
            ConfigMatch    => $ACLData->{ConfigMatch} || '',
            ConfigChange   => $ACLData->{ConfigChange} || '',
            StopAfterMatch => $ACLData->{StopAfterMatch} || 0,
            ValidID        => $ACLData->{ValidID},
            UserID         => $Self->{UserID},
        );

        # show error if can't create
        if ( !$ACLID ) {
            return $LayoutObject->ErrorScreen(
                Message => Translatable('There was an error creating the ACL'),
            );
        }

        # return to overview
        return $LayoutObject->Redirect( OP => "Action=$Self->{Action}" );
    }

    # ------------------------------------------------------------ #
    # Overview
    # ------------------------------------------------------------ #
    else {
        return $Self->_ShowOverview(
            %Param,
        );
    }
}

sub _ShowOverview {
    my ( $Self, %Param ) = @_;

    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
    my $ACLObject    = $Kernel::OM->Get('Kernel::System::ACL::DB::ACL');
    my $Output       = $LayoutObject->Header();
    $Output .= $LayoutObject->NavigationBar();

    if ( $Self->{UserID} == 1 ) {

        # show error notify, don't work with user id 1
        $Output .= $LayoutObject->Notify(
            Priority => 'Error',
            Info =>
                Translatable('Please note that ACL restrictions will be ignored for the Superuser account (UserID 1).'),
        );
    }

    # show notifications if any
    if ( $Param{NotifyData} ) {
        for my $Notification ( @{ $Param{NotifyData} } ) {
            $Output .= $LayoutObject->Notify(
                %{$Notification},
            );
        }
    }

    # get ACL list
    my $ACLList = $ACLObject->ACLList( UserID => $Self->{UserID} );

    if ( IsHashRefWithData($ACLList) ) {

        # sort data numerically
        my @ACLIDsSorted = sort { $ACLList->{$a} cmp $ACLList->{$b} } keys %{$ACLList};

        # get each ACLs data
        for my $ACLID (@ACLIDsSorted) {

            my $ACLData = $ACLObject->ACLGet(
                ID     => $ACLID,
                UserID => $Self->{UserID},
            );

            # set the valid state
            $ACLData->{ValidID}
                = $Kernel::OM->Get('Kernel::System::Valid')->ValidLookup( ValidID => $ACLData->{ValidID} );

            # print each ACL in overview table
            $LayoutObject->Block(
                Name => 'ACLRow',
                Data => {
                    %{$ACLData},
                },
            );
        }
    }
    else {

        # print no data found message
        $LayoutObject->Block(
            Name => 'ACLNoDataRow',
            Data => {},
        );
    }

    $Output .= $LayoutObject->Output(
        TemplateFile => 'AdminACL',
        Data         => \%Param,
    );
    $Output .= $LayoutObject->Footer();

    return $Output;
}

sub _ShowEdit {
    my ( $Self, %Param ) = @_;

    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');

    # get ACL information
    my $ACLData = $Param{ACLData} || {};

    # decide whether to show delete button
    if ( $Param{Action} eq 'Edit' && $ACLData && $ACLData->{ValidID} ne '1' ) {
        $LayoutObject->Block(
            Name => 'ACLDeleteAction',
            Data => {
                %{$ACLData},
            },
        );
    }

    # get valid list
    my %ValidList = $Kernel::OM->Get('Kernel::System::Valid')->ValidList();

    $Param{ValidOption} = $LayoutObject->BuildSelection(
        Data       => \%ValidList,
        Name       => 'ValidID',
        SelectedID => $ACLData->{ValidID} || $ValidList{valid},
        Class      => 'Modernize Validate_Required ' . ( $Param{Errors}->{'ValidIDInvalid'} || '' ),
    );

    my $ACLKeysLevel1Match = $ConfigObject->Get('ACLKeysLevel1Match') || {};
    $Param{ACLKeysLevel1Match} = $LayoutObject->BuildSelection(
        Data         => $ACLKeysLevel1Match,
        Name         => 'ItemAdd',
        Class        => 'Modernize ItemAdd ItemAddLevel1',
        ID           => 'ItemAddLevel1Match',
        TreeView     => 1,
        PossibleNone => 1,
        Translation  => 0,
    );

    my $ACLKeysLevel1Change = $ConfigObject->Get('ACLKeysLevel1Change') || {};
    $Param{ACLKeysLevel1Change} = $LayoutObject->BuildSelection(
        Data         => $ACLKeysLevel1Change,
        Name         => 'ItemAdd',
        Class        => 'Modernize ItemAdd ItemAddLevel1',
        ID           => 'ItemAddLevel1Change',
        TreeView     => 1,
        PossibleNone => 1,
        Translation  => 0,
    );

    my $ACLKeysLevel2Possible = $ConfigObject->Get('ACLKeysLevel2::Possible') || {};
    $Param{ACLKeysLevel2Possible} = $LayoutObject->BuildSelection(
        Data         => $ACLKeysLevel2Possible,
        Name         => 'ItemAdd',
        ID           => 'Possible',
        Class        => 'Modernize ItemAdd LevelToBeAdded',
        Translation  => 0,
        PossibleNone => 1,
    );

    my $ACLKeysLevel2PossibleAdd = $ConfigObject->Get('ACLKeysLevel2::PossibleAdd') || {};
    $Param{ACLKeysLevel2PossibleAdd} = $LayoutObject->BuildSelection(
        Data         => $ACLKeysLevel2PossibleAdd,
        Name         => 'ItemAdd',
        ID           => 'PossibleAdd',
        Class        => 'Modernize ItemAdd LevelToBeAdded',
        Translation  => 0,
        PossibleNone => 1,
    );

    my $ACLKeysLevel2PossibleNot = $ConfigObject->Get('ACLKeysLevel2::PossibleNot') || {};
    $Param{ACLKeysLevel2PossibleNot} = $LayoutObject->BuildSelection(
        Data         => $ACLKeysLevel2PossibleNot,
        Name         => 'ItemAdd',
        ID           => 'PossibleNot',
        Class        => 'Modernize ItemAdd LevelToBeAdded',
        Translation  => 0,
        PossibleNone => 1,
    );

    my $ACLKeysLevel2Properties = $ConfigObject->Get('ACLKeysLevel2::Properties') || {};
    $Param{ACLKeysLevel2Properties} = $LayoutObject->BuildSelection(
        Data         => $ACLKeysLevel2Properties,
        Name         => 'ItemAdd',
        ID           => 'Properties',
        Class        => 'Modernize ItemAdd LevelToBeAdded',
        Translation  => 0,
        PossibleNone => 1,
    );

    my $ACLKeysLevel2PropertiesDatabase = $ConfigObject->Get('ACLKeysLevel2::PropertiesDatabase') || {};
    $Param{ACLKeysLevel2PropertiesDatabase} = $LayoutObject->BuildSelection(
        Data         => $ACLKeysLevel2PropertiesDatabase,
        Name         => 'ItemAdd',
        ID           => 'PropertiesDatabase',
        Class        => 'Modernize ItemAdd LevelToBeAdded',
        Translation  => 0,
        PossibleNone => 1,
    );

    $Param{ACLKeysLevel4Prefixes} = $LayoutObject->BuildSelection(
        Data => {
            ''            => Translatable('Exact match'),
            '[Not]'       => Translatable('Negated exact match'),
            '[RegExp]'    => Translatable('Regular expression'),
            '[regexp]'    => Translatable('Regular expression (ignore case)'),
            '[NotRegExp]' => Translatable('Negated regular expression'),
            '[Notregexp]' => Translatable('Negated regular expression (ignore case)'),
        },
        Name           => 'ItemPrefix',
        Class          => 'ItemPrefix',
        ID             => 'Prefixes',
        Sort           => 'IndividualKey',
        SortIndividual => [ '', '[Not]', '[RegExp]', '[regexp]', '[NotRegExp]', '[Notregexp]' ],
        Translation    => 0,
        AutoComplete   => 'off',
    );

    my %DynamicFields;
    my $DynamicFieldList = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
        Valid      => 1,
        ObjectType => ['Ticket'],
    );

    DYNAMICFIELDCONFIG:
    for my $DynamicFieldConfig ( @{$DynamicFieldList} ) {
        next DYNAMICFIELDCONFIG if !IsHashRefWithData($DynamicFieldConfig);

        my $TranslatedLabel = $LayoutObject->{LanguageObject}->Translate( $DynamicFieldConfig->{Label} );
        my $CombinedLabel   = (
            $TranslatedLabel eq $DynamicFieldConfig->{Name}
            ? $TranslatedLabel
            : $TranslatedLabel . ' (' . $DynamicFieldConfig->{Name} . ')'
        );

        $DynamicFields{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $CombinedLabel;
    }

    %DynamicFields = map { $_ => $DynamicFields{$_} } sort keys %DynamicFields;

    $Param{ACLKeysLevel3DynamicFields} = $LayoutObject->BuildSelection(
        Data         => \%DynamicFields,
        Name         => 'NewDataKeyDropdown',
        Class        => 'Modernize NewDataKeyDropdown',
        ID           => 'DynamicField',
        Translation  => 0,
        PossibleNone => 1,
    );

    # get list of all possible actions
    my @PossibleActionsList;
    my $ACLKeysLevel3Actions = $ConfigObject->Get('ACLKeysLevel3::Actions') || [];

    for my $Key ( sort keys %{$ACLKeysLevel3Actions} ) {
        push @PossibleActionsList, @{ $ACLKeysLevel3Actions->{$Key} };
    }

    $Param{ACLKeysLevel3Actions} = $LayoutObject->BuildSelection(
        Data         => \@PossibleActionsList,
        Name         => 'NewDataKeyDropdown',
        Class        => 'Modernize NewDataKeyDropdown Boolean',
        ID           => 'Action',
        Translation  => 0,
        PossibleNone => 1,
    );

    $Param{PossibleActionsList} = \@PossibleActionsList;

    if ( defined $ACLData->{StopAfterMatch} && $ACLData->{StopAfterMatch} == 1 ) {
        $Param{Checked} = 'checked="checked"';
    }

    my $Output = $LayoutObject->Header();
    $Output .= $LayoutObject->NavigationBar();

    # show notifications if any
    if ( $Param{NotifyData} ) {
        for my $Notification ( @{ $Param{NotifyData} } ) {
            $Output .= $LayoutObject->Notify(
                %{$Notification},
            );
        }
    }

    # create data for JS
    my @ACLEditPossibleActionsList;
    for my $Item (@PossibleActionsList) {
        my %ACLEdit = (
            label => $Item,
            value => $Item
        );
        push @ACLEditPossibleActionsList, \%ACLEdit;
    }

    # set ACL data
    $LayoutObject->AddJSData(
        Key   => 'PossibleActionsList',
        Value => \@ACLEditPossibleActionsList,
    );

    $Output .= $LayoutObject->Output(
        TemplateFile => "AdminACL$Param{Action}",
        Data         => {
            %Param,
            %{$ACLData},
        },
    );

    $Output .= $LayoutObject->Footer();

    return $Output;
}

sub _GetParams {
    my ( $Self, %Param ) = @_;

    my $JSONObject = $Kernel::OM->Get('Kernel::System::JSON');
    my $GetParam;

    # get parameters from web browser
    for my $ParamName (
        qw( Name EntityID Comment Description StopAfterMatch ValidID ConfigMatch ConfigChange )
        )
    {
        $GetParam->{$ParamName} = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => $ParamName )
            || '';
    }

    if ( $GetParam->{ConfigMatch} ) {
        $GetParam->{ConfigMatch} = $JSONObject->Decode(
            Data => $GetParam->{ConfigMatch},
        );
        if ( !IsHashRefWithData( $GetParam->{ConfigMatch} ) ) {
            $GetParam->{ConfigMatch} = undef;
        }
    }

    if ( $GetParam->{ConfigChange} ) {
        $GetParam->{ConfigChange} = $JSONObject->Decode(
            Data => $GetParam->{ConfigChange},
        );
        if ( !IsHashRefWithData( $GetParam->{ConfigChange} ) ) {
            $GetParam->{ConfigChange} = undef;
        }
    }

    return $GetParam;
}

sub _CheckACLDelete {
    my ( $Self, %Param ) = @_;

    # get ACL data
    my $ACLData = $Kernel::OM->Get('Kernel::System::ACL::DB::ACL')->ACLGet(
        ID     => $Param{ID},
        UserID => $Self->{UserID},
    );

    # check for valid ACL data
    if ( !IsHashRefWithData($ACLData) ) {
        return {
            Success => 0,
            Message => "Could not get data for ACLID $Param{ID}",
        };
    }

    return {
        Success => 1,
        ACLData => $ACLData,
    };
}

1;
