# --
# Modified version of the work: Copyright (C) 2006-2024 KIX Service Software GmbH, https://www.kixdesk.com
# based on the original work of:
# Copyright (C) 2001-2017 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file LICENSE-AGPL for license information (AGPL). If you
# did not receive this file, see https://www.gnu.org/licenses/agpl.txt.
# --

package Kernel::System::Ticket::Event::NotificationEvent;

use strict;
use warnings;

use List::Util qw(first);
use Time::HiRes qw(time);

use base qw(Kernel::System::AsynchronousExecutor);

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

our @ObjectDependencies = (
    'Config',
    'Contact',
    'DynamicField',
    'DynamicField::Backend',
    'Email',
    'JSON',
    'Log',
    'Main',
    'NotificationEvent',
    'Role',
    'Queue',
    'TemplateGenerator',
    'Ticket',
    'Time',
    'User',
);

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

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

    return $Self;
}

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

    # check needed stuff
    for my $Needed (qw(Event Data Config UserID)) {
        if ( !$Param{$Needed} ) {
            return if $Param{Silent};
            $Kernel::OM->Get('Log')->Log(
                Priority => 'error',
                Message  => "Need $Needed!",
            );
            return;
        }
    }

    # check if event is affected
    if ( !IsHashRef($Self->{NotificationEventMapping}) ) {
        $Self->{NotificationEventMapping} = { $Kernel::OM->Get('NotificationEvent')->NotificationEventList() };
    }
    return if !IsArrayRefWithData($Self->{NotificationEventMapping}->{$Param{Event}});

    if (
        !$Param{Data}->{TicketID}
        && !IsArrayRefWithData($Param{Data}->{TicketList})
    ) {
        return if $Param{Silent};
        $Kernel::OM->Get('Log')->Log(
            Priority => 'error',
            Message  => 'Need TicketID or TicketList in Data!',
        );
        return;
    }

    # return if no notification is active
    return 1 if $Kernel::OM->Get('Ticket')->{SendNoNotification};

    if ( $Param{Data}->{TicketID} ) {
        # return if no ticket exists (e. g. it got deleted)
        my $TicketNumber = $Kernel::OM->Get('Ticket')->TicketNumberLookup(
            TicketID => $Param{Data}->{TicketID},
            UserID   => $Param{UserID},
        );

        return 1 if !$TicketNumber;
    }

    my $Result;

    if (
        !$ENV{IsDaemon}
        && $Kernel::OM->Get('Config')->Get('TicketNotification::SendAsynchronously')
    ) {
        my $MaximumParallelInstances = $Kernel::OM->Get('Config')->Get('TicketNotification::SendAsynchronously::MaximumParallelInstances') || 0;
        $Result = $Self->AsyncCall(
            FunctionName             => '_Run',
            FunctionParams           => \%Param,
            MaximumParallelInstances => $MaximumParallelInstances
        );
        if ( !$Result ) {
            $Kernel::OM->Get('Log')->Log(
                Priority => 'error',
                Message  => "Could not schedule asynchronous NotificationEvent execution!",
            );
        }
    }
    else {
        $Result = $Self->_Run(
            %Param
        );
    }

    return $Result;
}

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

    my $StartTime = Time::HiRes::time();

    # check if event is affected
    if ( !IsHashRef($Self->{NotificationEventMapping}) ) {
        $Self->{NotificationEventMapping} = { $Kernel::OM->Get('NotificationEvent')->NotificationEventList() };
    }
    return if !IsArrayRefWithData($Self->{NotificationEventMapping}->{$Param{Event}});

    my @TicketList = IsArrayRef($Param{Data}->{TicketList}) ? @{$Param{Data}->{TicketList}} : ( $Param{Data} );

    my $Counter = 0;

    TICKET:
    foreach my $Ticket ( @TicketList ) {
        next TICKET if !$Ticket->{TicketID};

        my $StartTimeTicket = Time::HiRes::time();
        $Self->_HandleTicket(
            Event  => $Param{Event},
            Data   => $Ticket,
            UserID => $Param{UserID},
        );
        $Kernel::OM->Get('Log')->Log(
            Priority => 'debug',
            Message  => sprintf "NotificationEvent::_HandleTicket %i/%i (TicketID: %i): %i ms\n", ++$Counter, (scalar @TicketList), $Ticket->{TicketID}, (Time::HiRes::time() - $StartTimeTicket) * 1000,
        );
    }

    $Kernel::OM->Get('Log')->Log(
        Priority => 'debug',
        Message  => sprintf "NotificationEvent::_Run (%i tickets): %i ms\n", (scalar @TicketList), (Time::HiRes::time() - $StartTime) * 1000,
    );

    return 1;
}

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

    # get ticket attribute matches
    my %Ticket = $Kernel::OM->Get('Ticket')->TicketGet(
        TicketID      => $Param{Data}->{TicketID},
        UserID        => $Param{UserID},
        DynamicFields => 0,
    );

    NOTIFICATION:
    for my $ID ( @{$Self->{NotificationEventMapping}->{$Param{Event}} || []} ) {

        my $InformedRecipientCount = 0;

        my %Notification = $Kernel::OM->Get('NotificationEvent')->NotificationGet(
            ID => $ID,
        );

        # verify ticket and article conditions
        my $PassFilter = $Self->_NotificationFilter(
            %Param,
            Ticket       => \%Ticket,
            Notification => \%Notification,
        );
        next NOTIFICATION if !$PassFilter;

        # add attachments only on ArticleCreate or ArticleSend event
        my @Attachments;
        if (
            (
                $Param{Event} eq 'ArticleCreate'
                || $Param{Event} eq 'ArticleSend'
            )
            && $Param{Data}->{ArticleID}
        ) {

            # add attachments to notification
            if ( $Notification{Data}->{ArticleAttachmentInclude}->[0] ) {

                # get article, it is needed for the correct behavior of the
                # StripPlainBodyAsAttachment flag into the ArticleAttachmentIndex function
                my %Article = $Kernel::OM->Get('Ticket')->ArticleGet(
                    ArticleID     => $Param{Data}->{ArticleID},
                    UserID        => $Param{UserID},
                    DynamicFields => 0,
                );

                my %Index = $Kernel::OM->Get('Ticket')->ArticleAttachmentIndex(
                    ArticleID                  => $Param{Data}->{ArticleID},
                    Article                    => \%Article,
                    UserID                     => $Param{UserID},
                    StripPlainBodyAsAttachment => 3,
                );
                if (%Index) {
                    FILE_ID:
                    for my $FileID ( sort keys %Index ) {
                        my %Attachment = $Kernel::OM->Get('Ticket')->ArticleAttachment(
                            ArticleID    => $Param{Data}->{ArticleID},
                            AttachmentID => $FileID,
                            UserID       => $Param{UserID},
                        );
                        next FILE_ID if !%Attachment;

                        # remove HTML-Attachments (HTML-Emails)
                        next if (
                            $Index{$FileID}->{Filename} =~ /^file-[12]$/
                            && $Index{$FileID}->{ContentType} =~ /text\/html/i
                        );

                        push @Attachments, \%Attachment;
                    }
                }
            }
        }

        # get recipients
        my @RecipientUsers = $Self->_RecipientsGet(
            %Param,
            Ticket       => \%Ticket,
            Notification => \%Notification,
        );

        my @NotificationBundle;

        # parse all notification tags for each user
        for my $Recipient (@RecipientUsers) {

            my $UserNotificationTransport = $Kernel::OM->Get('JSON')->Decode(
                Data => $Recipient->{Preferences}->{NotificationTransport},
            );

            push @NotificationBundle, {
                Recipient                      => $Recipient,
                Notification                   => \%Notification,
                RecipientNotificationTransport => $UserNotificationTransport,
            };
        }

        # get notification transport config
        my %TransportConfig = %{ $Kernel::OM->Get('Config')->Get('Notification::Transport') || {} };

        # remember already sent agent notifications
        my %AlreadySent;

        # loop over transports for each notification
        TRANSPORT:
        for my $Transport ( sort keys %TransportConfig ) {

            # only configured transports for this notification
            if ( !grep { $_ eq $Transport } @{ $Notification{Data}->{Transports} } ) {
                next TRANSPORT;
            }

            next TRANSPORT if !IsHashRefWithData( $TransportConfig{$Transport} );
            next TRANSPORT if !$TransportConfig{$Transport}->{Module};

            # get transport object
            my $TransportObject;
            eval {
                $TransportObject = $Kernel::OM->Get( $TransportConfig{$Transport}->{Module} );
            };

            if ( !$TransportObject ) {
                $Kernel::OM->Get('Log')->Log(
                    Priority => 'error',
                    Message  => "Could not create a new $TransportConfig{$Transport}->{Module} object!",
                );

                next TRANSPORT;
            }

            if ( ref $TransportObject ne $TransportConfig{$Transport}->{Module} ) {
                $Kernel::OM->Get('Log')->Log(
                    Priority => 'error',
                    Message  => "$TransportConfig{$Transport}->{Module} object is invalid",
                );

                next TRANSPORT;
            }

            # check if transport is usable
            next TRANSPORT if !$TransportObject->IsUsable();

            BUNDLE:
            for my $Bundle (@NotificationBundle) {
                my $UserPreference = "Notification-$Notification{ID}-$Transport";

                # check if agent should get the notification
                my $AgentSendNotification = 0;
                if ( defined $Bundle->{RecipientNotificationTransport}->{$UserPreference} ) {
                    $AgentSendNotification = $Bundle->{RecipientNotificationTransport}->{$UserPreference};
                }
                elsif ( grep { $_ eq $Transport } @{ $Notification{Data}->{AgentEnabledByDefault} } ) {
                    $AgentSendNotification = 1;
                }
                elsif (
                    !IsArrayRefWithData( $Notification{Data}->{VisibleForAgent} )
                    || (
                        defined $Notification{Data}->{VisibleForAgent}->[0]
                        && !$Notification{Data}->{VisibleForAgent}->[0]
                    )
                ) {
                    $AgentSendNotification = 1;
                }

                # skip sending the notification if the agent has disabled it in its preferences
                if (
                    IsArrayRefWithData( $Notification{Data}->{VisibleForAgent} )
                    && $Notification{Data}->{VisibleForAgent}->[0]
                    && $Bundle->{Recipient}->{Type} eq 'Agent'
                    && !$AgentSendNotification
                ) {
                    next BUNDLE;
                }

                # Check if notification should not be send to the customer.
                if (
                    $Bundle->{Recipient}->{Type} eq 'Customer'
                    && $Kernel::OM->Get('Config')->Get('CustomerNotifyJustToRealCustomer')
                ) {
                    # No UserID means it's not a mapped customer.
                    next BUNDLE if ( !$Bundle->{Recipient}->{UserID} );
                }

                my $Success = $Self->_SendRecipientNotification(
                    TicketID              => $Param{Data}->{TicketID},
                    ArticleID             => $Param{Data}->{ArticleID},
                    Notification          => $Bundle->{Notification},
                    CustomerMessageParams => $Param{Data}->{CustomerMessageParams} || {},
                    Recipient             => $Bundle->{Recipient},
                    Event                 => $Param{Event},
                    Attachments           => \@Attachments,
                    Transport             => $Transport,
                    TransportObject       => $TransportObject,
                    UserID                => $Param{UserID},
                );

                $InformedRecipientCount++;

                # remember to have sent
                if ( $Bundle->{Recipient}->{UserID} ) {
                    $AlreadySent{ $Bundle->{Recipient}->{UserID} } = 1;
                }
            }

            # get special recipients specific for each transport
            my @TransportRecipients = $TransportObject->GetTransportRecipients(
                Notification => \%Notification,
                TicketID     => $Param{Data}->{TicketID},
                ArticleID    => $Param{Data}->{ArticleID}
            );

            next TRANSPORT if !@TransportRecipients;

            RECIPIENT:
            for my $Recipient (@TransportRecipients) {

                my $Success = $Self->_SendRecipientNotification(
                    TicketID              => $Param{Data}->{TicketID},
                    ArticleID             => $Param{Data}->{ArticleID},
                    Notification          => \%Notification,
                    CustomerMessageParams => $Param{Data}->{CustomerMessageParams} || {},
                    Recipient             => $Recipient,
                    Event                 => $Param{Event},
                    Attachments           => \@Attachments,
                    Transport             => $Transport,
                    TransportObject       => $TransportObject,
                    UserID                => $Param{UserID},
                );

                $InformedRecipientCount++;
            }
        }

        $Kernel::OM->Get('Log')->Log(
            Priority => 'info',
            Message  => sprintf "NotificationEvent::_HandleTicket: informed %i recipients (notification \"%s\")\n", $InformedRecipientCount, ($Notification{Name} || '- No name -'),
        );
    }

    return 1;
}

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

    # check needed params
    for my $Needed (qw(Data Notification)) {
        return if !$Param{$Needed};
    }

    my $Filter = $Param{Notification}->{Filter};

    # create or extend the filter with the ArticleID
    if ( $Param{Data}->{ArticleID} ) {
        # add ArticleID to filter
        $Filter //= {};
        $Filter->{AND} //= [];
        push @{$Filter->{AND}}, {
            Field    => 'ArticleID',
            Operator => 'EQ',
            Value    => $Param{Data}->{ArticleID}
        };
    }
    # create or extend the filter with the TicketID
    if ( $Param{Data}->{TicketID} ) {
        # add TicketID to filter
        $Filter //= {};
        $Filter->{AND} //= [];
        push @{$Filter->{AND}}, {
            Field    => 'TicketID',
            Operator => 'EQ',
            Value    => $Param{Data}->{TicketID}
        };
    }

    # do the search
    my @TicketIDs = $Kernel::OM->Get('ObjectSearch')->Search(
        ObjectType => 'Ticket',
        Result     => 'ARRAY',
        Search     => $Filter,
        Limit      => 1,
        UserID     => 1,
        UserType   => 'Agent'
    );

    return @TicketIDs && $TicketIDs[0] == $Param{Data}->{TicketID};
}

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

    # check needed params
    for my $Needed (qw(Ticket Notification)) {
        return if !$Param{$Needed};
    }

    # set local values
    my %Notification = %{ $Param{Notification} };
    my %Ticket       = %{ $Param{Ticket} };

    my @RecipientUserIDs;
    my @RecipientUsers;

    # add pre-calculated recipient
    if ( IsArrayRefWithData( $Param{Data}->{Recipients} ) ) {
        push @RecipientUserIDs, @{ $Param{Data}->{Recipients} };
    }

    # remember pre-calculated user recipients for later comparisons
    my %PrecalculatedUserIDs = map { $_ => 1 } @RecipientUserIDs;

    # get recipients by Recipients
    if ( $Notification{Data}->{Recipients} ) {
        RECIPIENT:
        for my $Recipient ( @{ $Notification{Data}->{Recipients} } ) {
            if ( $Recipient =~ /^Agent(Owner|Responsible|Watcher|ReadPermissions|WritePermissions|MyQueues|MyServices|MyQueuesMyServices|)$/ ) {
                if ( $Recipient eq 'AgentOwner' ) {
                    push @{ $Notification{Data}->{RecipientAgents} }, $Ticket{OwnerID};
                }
                elsif ( $Recipient eq 'AgentResponsible' ) {
                    # add the responsible agent to the notification list
                    if ( $Kernel::OM->Get('Config')->Get('Ticket::Responsible') && $Ticket{ResponsibleID} ) {

                        push @{ $Notification{Data}->{RecipientAgents} },
                            $Ticket{ResponsibleID};
                    }
                }
                elsif ( $Recipient eq 'AgentWatcher' ) {
                    # its checked on WatcherList function
                    push @{ $Notification{Data}->{RecipientAgents} }, map { $_->{UserID} } $Kernel::OM->Get('Watcher')->WatcherList(
                        Object   => 'Ticket',
                        ObjectID => $Param{Data}->{TicketID},
                    );
                }
                elsif ( $Recipient eq 'AgentReadPermissions' ) {
                    my @UserIDs = $Self->_GetUserIDsWithRequiredPermission(
                        Ticket     => \%Ticket,
                        Permission => 'READ',
                        Strict     => 1,    # READONLY permission needed
                    );

                    push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
                }
                elsif ( $Recipient eq 'AgentWritePermissions' ) {
                    my @UserIDs = $Self->_GetUserIDsWithRequiredPermission(
                        Ticket     => \%Ticket,
                        Permission => 'WRITE,READ'
                    );

                    push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
                }
                elsif ( $Recipient eq 'AgentMyQueues' ) {
                    my @UserIDs = $Self->_GetSubscribedUserIDsByQueueID(
                        QueueID => $Ticket{QueueID}
                    );
                    push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
                }
                elsif ( $Recipient eq 'AgentMyServices' ) {
                    my @UserIDs = $Self->_GetSubscribedUserIDsByServiceID(
                        ServiceID => $Ticket{ServiceID}
                    );
                    push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
                }
                elsif ( $Recipient eq 'AgentMyQueuesMyServices' ) {
                    # get subscribed users
                    my @UserIDs = $Self->_GetSubscribedUserIDsByQueueID(
                        QueueID => $Ticket{QueueID}
                    );
                    my %MyQueuesUserIDs = map { $_ => 1 } @UserIDs;

                    @UserIDs = $Self->_GetSubscribedUserIDsByServiceID(
                        ServiceID => $Ticket{ServiceID}
                    );
                    my %MyServicesUserIDs = map { $_ => 1 } @UserIDs;

                    # combine both subscribed users list (this will also remove duplicates)
                    my %SubscribedUserIDs = ( %MyQueuesUserIDs, %MyServicesUserIDs );

                    for my $UserID ( sort keys %SubscribedUserIDs ) {
                        if ( !$MyQueuesUserIDs{$UserID} || !$MyServicesUserIDs{$UserID} ) {
                            delete $SubscribedUserIDs{$UserID};
                        }
                    }

                    @UserIDs = sort keys %SubscribedUserIDs;

                    push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
                }
            }

            elsif ( $Recipient eq 'Customer' ) {
                # get old article for quoting
                my %Article = $Kernel::OM->Get('Ticket')->ArticleLastCustomerArticle(
                    TicketID      => $Param{Data}->{TicketID},
                    DynamicFields => 0,
                );

                # get the raw ticket data
                my %Ticket = $Kernel::OM->Get('Ticket')->TicketGet(
                    TicketID      => $Param{Data}->{TicketID},
                    DynamicFields => 0,
                );

                my %Recipient;

                # Check if we actually do have an article
                if ( defined $Article{SenderType} ) {
                    if ( $Article{SenderType} eq 'external' ) {
                        $Recipient{Email} = $Article{From};
                    }
                    else {
                        $Recipient{Email} = $Article{To};
                    }
                }
                $Recipient{Type} = 'Customer';

                # check if customer notifications should be send
                if (
                    $Kernel::OM->Get('Config')->Get('CustomerNotifyJustToRealCustomer')
                    && !$Ticket{ContactID}
                ) {
                    $Kernel::OM->Get('Log')->Log(
                        Priority => 'info',
                        Message  => 'Send no customer notification because no customer is set!',
                    );
                    next RECIPIENT;
                }

                # get language and send recipient
                $Recipient{UserLanguage} = $Kernel::OM->Get('Config')->Get('DefaultLanguage') || 'en';

                if ( $Ticket{ContactID} ) {

                    my %Contact = $Kernel::OM->Get('Contact')->ContactGet(
                        ID => $Ticket{ContactID},
                    );

                    # join Recipient data with Contact data
                    %Recipient = ( %Recipient, %Contact );

                    # set recipient language by assigned user
                    if ( $Contact{AssignedUserID} ) {
                        my $Language = $Kernel::OM->Get('User')->GetUserLanguage(
                            UserID => $Contact{AssignedUserID}
                        );
                        if ( $Language ) {
                            $Recipient{UserLanguage} = $Language;
                        }
                    }

                    $Recipient{Realname} = $Contact{Firstname}
                        . q{ }
                        . $Contact{Lastname};
                }

                if ( !$Recipient{Realname} ) {
                    $Recipient{Realname} = $Article{From} || q{};
                    $Recipient{Realname} =~ s/<.*>|\(.*\)|\"|;|,//g;
                    $Recipient{Realname} =~ s/( $)|(  $)//g;
                }

                push( @RecipientUsers, \%Recipient );
            }
        }
    }

    # add recipient agents
    if ( IsArrayRefWithData( $Notification{Data}->{RecipientAgents} ) ) {
        push( @RecipientUserIDs, @{ $Notification{Data}->{RecipientAgents} } );
    }

    # hash to keep track which agents are already receiving this notification
    my %AgentUsed = map { $_ => 1 } @RecipientUserIDs;

    # get recipients by RecipientRoles
    if ( $Notification{Data}->{RecipientRoles} ) {

        RECIPIENT:
        for my $RoleID ( @{ $Notification{Data}->{RecipientRoles} } ) {

            my @RoleMemberList = $Kernel::OM->Get('Role')->RoleUserList(
                RoleID => $RoleID,
            );

            ROLEMEMBER:
            for my $UserID ( sort @RoleMemberList ) {

                next ROLEMEMBER if $UserID == 1;
                next ROLEMEMBER if $AgentUsed{$UserID};

                $AgentUsed{$UserID} = 1;

                push @RecipientUserIDs, $UserID;
            }
        }
    }

    # remove duplicates
    @RecipientUserIDs = $Kernel::OM->Get('Main')->GetUnique( @RecipientUserIDs );

    my %SkipRecipients;
    if ( IsArrayRefWithData( $Param{Data}->{SkipRecipients} ) ) {
        %SkipRecipients = map { $_ => 1 } @{ $Param{Data}->{SkipRecipients} };
    }

    # user 1 should not receive notifications
    $SkipRecipients{'1'} = 1;

    # user of PostMasterUserID should not receive notifications
    my $PostmasterUserID = $Kernel::OM->Get('Config')->Get('PostmasterUserID') || 1;
    $SkipRecipients{ $PostmasterUserID } = 1;

    # remove recipients should not receive a notification
    @RecipientUserIDs = grep { !$SkipRecipients{$_} } @RecipientUserIDs;

    # get valid users list
    my %ValidUsersList = $Kernel::OM->Get('User')->UserList(
        Type  => 'Short',
        Valid => 1,
    );

    # keep only valid users
    @RecipientUserIDs = grep { $ValidUsersList{$_} } @RecipientUserIDs;

    # skip users that are out of office, if not enforced
    if (
        !IsArrayRefWithData($Notification{Data}->{SendOnOutOfOffice})
        || !$Notification{Data}->{SendOnOutOfOffice}->[0]
    ) {
        my %OutOfOfficeUsersList = $Kernel::OM->Get('User')->UserSearch(
            IsOutOfOffice => 1,
            Valid         => 1,
        );

        # remove out of office users
        @RecipientUserIDs = grep { !$OutOfOfficeUsersList{$_} } @RecipientUserIDs;
    }

    # get date string for current date at begin of day
    my $CurrDayBegin = $Kernel::OM->Get('Time')->CurrentTimestamp();
    $CurrDayBegin =~ s/^(\d{4}-\d{2}-\d{2}).+$/$1 00:00:00/;

    # get all data for recipients as they should be needed by all notification transports
    RECIPIENT:
    for my $UserID ( @RecipientUserIDs ) {
        # skip user that triggers the event (it should not be notified) but only if it is not
        #   a pre-calculated recipient
        if (
            !$Kernel::OM->Get('Config')->Get('AgentSelfNotifyOnAction')
            && $UserID == $Param{UserID}
            && !$PrecalculatedUserIDs{ $Param{UserID} }
        ) {
            next RECIPIENT;
        }

        # check for required permission
        my $Granted;
        my $Resource = '/tickets/' . $Ticket{TicketID};
        if ( exists( $Self->{Cache}->{UserPermission}->{ $UserID }->{ $Resource }->{READ} ) ) {
            $Granted = $Self->{Cache}->{UserPermission}->{ $UserID }->{ $Resource }->{READ};
        }
        else {
            # skip users with out READ permissions
            $Granted = $Kernel::OM->Get('User')->CheckResourcePermission(
                UserID              => $UserID,
                Target              => $Resource,
                UsageContext        => 'Agent',
                RequestedPermission => 'READ'
            );
        }
        next RECIPIENT if ( !$Granted );

        # get data of recipient user
        my %User = $Kernel::OM->Get('User')->GetUserData(
            UserID => $UserID,
            Valid  => 1,
        );
        next RECIPIENT if ( !%User );

        # check if the notification needs to be sent just one time per day
        if (
            IsArrayRefWithData( $Notification{Data}->{OncePerDay} )
            && $Notification{Data}->{OncePerDay}->[0]
            && $User{UserLogin}
        ) {
            # get ticket history
            my @HistoryLines = $Kernel::OM->Get('Ticket')->HistoryGet(
                TicketID      => $Param{Data}->{TicketID},
                HistoryType   => 'SendAgentNotification',
                Name          => "\%\%$Notification{Name}\%\%$User{UserLogin}\%\%Email",      # we use a fixed Email here
                MinCreateTime => $CurrDayBegin,
                SortReverse   => 1,
                UserID        => $Param{UserID},
                Limit         => 1,
            );

            # do not send the notification if it has been sent already today
            next RECIPIENT if ( @HistoryLines );
        }

        # set recipient language
        my $Language = $Kernel::OM->Get('User')->GetUserLanguage(UserID => $UserID);
        if ( $Language ) {
            $User{UserLanguage} = $Language;
        }

        # set recipient type to Agent
        $User{Type} = 'Agent';

        push( @RecipientUsers, \%User );
    }

    return @RecipientUsers;
}

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

    # check needed stuff
    for my $Needed (qw(TicketID UserID Notification Recipient Event Transport TransportObject)) {
        if ( !$Param{$Needed} ) {
            $Kernel::OM->Get('Log')->Log(
                Priority => 'error',
                Message  => "Need $Needed!",
            );
        }
    }

    my $TransportObject = $Param{TransportObject};

    my %ReplacedNotification = $Kernel::OM->Get('TemplateGenerator')->NotificationEvent(
        TicketID              => $Param{TicketID},
        ArticleID             => $Param{ArticleID},
        Recipient             => $Param{Recipient},
        Notification          => $Param{Notification},
        CustomerMessageParams => $Param{CustomerMessageParams},
        UserID                => $Param{UserID},
    );

    # send notification to each recipient
    my $Success = $TransportObject->SendNotification(
        TicketID              => $Param{TicketID},
        UserID                => $Param{UserID},
        Notification          => \%ReplacedNotification,
        CustomerMessageParams => $Param{CustomerMessageParams},
        Recipient             => $Param{Recipient},
        Event                 => $Param{Event},
        Attachments           => $Param{Attachments},
    );

    return if !$Success;

    # create separate history entries if no article has been created
    if ( !IsArrayRefWithData($Param{Notification}->{Data}->{CreateArticle}) || !$Param{Notification}->{Data}->{CreateArticle}->[0] ) {
        if (
            $Param{Recipient}->{Type} eq 'Agent'
            && $Param{Recipient}->{UserLogin}
        ) {
            # write history
            $Kernel::OM->Get('Ticket')->HistoryAdd(
                TicketID     => $Param{TicketID},
                HistoryType  => 'SendAgentNotification',
                Name         => "\%\%$Param{Notification}->{Name}\%\%$Param{Recipient}->{UserLogin}\%\%$Param{Transport}",
                CreateUserID => $Param{UserID},
            );
        }
        elsif (
            $Param{Recipient}->{Type} eq 'Customer'
            && $Param{Recipient}->{Email}
        ) {
            # write history
            $Kernel::OM->Get('Ticket')->HistoryAdd(
                TicketID     => $Param{TicketID},
                HistoryType  => 'SendCustomerNotification',
                Name         => "\%\%$Param{Recipient}->{Email}",
                CreateUserID => $Param{UserID},
            );
        }
    }

    my %EventData = %{ $TransportObject->GetTransportEventData() };

    return 1 if !%EventData;

    if ( !$EventData{Event} || !$EventData{Data} || !$EventData{UserID} ) {

        $Kernel::OM->Get('Log')->Log(
            Priority => 'error',
            Message  => "Could not trigger notification post send event",
        );

        return;
    }

    # ticket event
    $Kernel::OM->Get('Ticket')->EventHandler(
        %EventData,
    );

    return 1;
}

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

    my @BasePermissionUserIDs = $Kernel::OM->Get('Ticket')->BasePermissionRelevantQueueUserIDList(
        QueueID       => $Param{Ticket}->{QueueID},
        Permission    => $Param{Permission},
        Strict        => $Param{Strict}
    );

    my $Resource = '/tickets/' . $Param{Ticket}->{TicketID};

    my @UserIDs = ();

    USERID:
    foreach my $UserID (@BasePermissionUserIDs) {
        my @Permissions = split(/, ?/, $Param{Permission});

        PERMISSION:
        foreach my $Permission (@Permissions) {
            my $Granted;
            if ( exists $Self->{Cache}->{UserPermission}->{$UserID}->{$Resource}->{$Permission} ) {
                $Granted = $Self->{Cache}->{UserPermission}->{$UserID}->{$Resource}->{$Permission};
            } else {
                $Granted = $Kernel::OM->Get('User')->CheckResourcePermission(
                    UserID              => $UserID,
                    Target              => $Resource,
                    UsageContext        => 'Agent',
                    RequestedPermission => $Permission
                );
                $Self->{Cache}->{UserPermission}->{$UserID}->{Resource}->{$Resource}->{$Permission} = $Granted;
            }

            next USERID if !$Granted;
        }

        push @UserIDs, $UserID;
    }

    return @UserIDs;
}

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

    my @UserIDs;
    if ( IsArrayRef($Self->{Cache}->{SubscribedUserIDsByQueueID}->{$Param{QueueID}}) ) {
        @UserIDs = @{$Self->{Cache}->{SubscribedUserIDsByQueueID}->{$Param{QueueID}}};
    }
    else {
        # get subscribed users
        my %MyQueuesUserIDs = map { $_ => 1 } $Kernel::OM->Get('Ticket')->GetSubscribedUserIDsByQueueID(
            QueueID => $Param{QueueID}
        );

        @UserIDs = sort keys %MyQueuesUserIDs;
        $Self->{Cache}->{SubscribedUserIDsByQueueID}->{$Param{QueueID}} = \@UserIDs;
    }

    return @UserIDs;
}

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

    my @UserIDs;
    if ( $Param{ServiceID} ) {
        if ( IsArrayRef($Self->{Cache}->{SubscribedUserIDsByServiceID}->{$Param{ServiceID}}) ) {
            @UserIDs = @{$Self->{Cache}->{SubscribedUserIDsByServiceID}->{$Param{ServiceID}}};
        }
        else {
            # get subscribed users
            my %MyServicesUserIDs = map { $_ => 1 } $Kernel::OM->Get('Ticket')->GetSubscribedUserIDsByServiceID(
                ServiceID => $Param{ServiceID},
            );

            @UserIDs = sort keys %MyServicesUserIDs;
            $Self->{Cache}->{SubscribedUserIDsByServiceID}->{$Param{ServiceID}} = \@UserIDs;
        }
    }

    return @UserIDs;
}

1;


=back

=head1 TERMS AND CONDITIONS

This software is part of the KIX project
(L<https://www.kixdesk.com/>).

This software comes with ABSOLUTELY NO WARRANTY. For details, see the enclosed file
LICENSE-AGPL for license information (AGPL). If you did not receive this file, see

<https://www.gnu.org/licenses/agpl.txt>.

=cut
