# --
# Copyright (C) 2001-2018 OTRS AG, http://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (AGPL). If you
# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
# --

package Kernel::System::NotificationEvent;

use strict;
use warnings;

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

our @ObjectDependencies = (
    'Kernel::System::DB',
    'Kernel::System::Log',
    'Kernel::System::Valid',
    'Kernel::System::YAML',
    'Kernel::System::Cache'
);

=head1 NAME

Kernel::System::NotificationEvent - to manage the notifications

=head1 DESCRIPTION

All functions to manage the notification and the notification jobs.

=head1 PUBLIC INTERFACE

=head2 new()

Don't use the constructor directly, use the ObjectManager instead:

    my $NotificationEventObject = $Kernel::OM->Get('Kernel::System::NotificationEvent');

=cut

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

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

    $Self->{CacheType} = 'NotificationEvent';
    $Self->{CacheTTL}  = 60 * 60 * 24 * 20;
    return $Self;
}

=head2 NotificationList()

returns a hash of all notifications

    my %List = $NotificationEventObject->NotificationList(
        Type    => 'Ticket', # type of notifications; default: 'Ticket'
        Details => 1,        # include notification detailed data. possible (0|1) # ; default: 0
        All     => 1,        # optional: if given all notification types will be returned, even if type is given (possible: 0|1)
    );

=cut

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

    $Param{Type} ||= 'Ticket';
    $Param{Details} = $Param{Details} ? 1 : 0;
    $Param{All}     = $Param{All}     ? 1 : 0;

    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');

    my $CacheKey    = $Self->{CacheType} . '::' . $Param{Type} . '::' . $Param{Details} . '::' . $Param{All};
    my $CacheResult = $CacheObject->Get(
        Type => $Self->{CacheType},
        Key  => $CacheKey,
    );

    if ( ref $CacheResult eq 'HASH' ) {
        return %{$CacheResult};
    }

    # get database object
    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');

    $DBObject->Prepare( SQL => 'SELECT id FROM notification_event' );

    my @NotificationList;
    while ( my @Row = $DBObject->FetchrowArray() ) {
        push @NotificationList, $Row[0];
    }

    my %Result;

    ITEMID:
    for my $ItemID ( sort @NotificationList ) {

        my %NotificationData = $Self->NotificationGet(
            ID     => $ItemID,
            UserID => 1,
        );

        $NotificationData{Data}->{NotificationType} ||= ['Ticket'];

        if ( !$Param{All} ) {
            next ITEMID if $NotificationData{Data}->{NotificationType}->[0] ne $Param{Type};
        }

        if ( $Param{Details} ) {
            $Result{$ItemID} = \%NotificationData;
        }
        else {
            $Result{$ItemID} = $NotificationData{Name};
        }
    }

    $CacheObject->Set(
        Type  => $Self->{CacheType},
        Key   => $CacheKey,
        Value => \%Result,
        TTL   => $Self->{CacheTTL},
    );

    return %Result;
}

=head2 NotificationGet()

returns a hash of the notification data

    my %Notification = $NotificationEventObject->NotificationGet(
        Name => 'NotificationName',
    );

    my %Notification = $NotificationEventObject->NotificationGet(
        ID => 1,
    );

Returns:

    %Notification = (
        ID      => 123,
        Name    => 'Agent::Move',
        Data => {
            Events => [ 'TicketQueueUpdate' ],
            ...
            Queue => [ 'SomeQueue' ],
        },
        Message => {
            en => {
                Subject     => 'Hello',
                Body        => 'Hello World',
                ContentType => 'text/plain',
            },
            de => {
                Subject     => 'Hallo',
                Body        => 'Hallo Welt',
                ContentType => 'text/plain',
            },
        },
        Comment    => 'An optional comment',
        ValidID    => 1,
        CreateTime => '2010-10-27 20:15:00',
        CreateBy   => 2,
        ChangeTime => '2010-10-27 20:15:00',
        ChangeBy   => 1,
        UserID     => 3,
    );

=cut

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

    # check needed stuff
    if ( !$Param{Name} && !$Param{ID} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => 'Need Name or ID!',
        );
        return;
    }

    # get database object
    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');

    # general query structure
    my $SQL = '
        SELECT id, name, valid_id, comments, create_time, create_by, change_time, change_by
        FROM notification_event
        WHERE ';

    if ( $Param{Name} ) {

        $DBObject->Prepare(
            SQL  => $SQL . 'name = ?',
            Bind => [ \$Param{Name} ],
        );
    }
    else {
        $DBObject->Prepare(
            SQL  => $SQL . 'id = ?',
            Bind => [ \$Param{ID} ],
        );
    }

    # get notification event data
    my %Data;
    while ( my @Row = $DBObject->FetchrowArray() ) {
        $Data{ID}         = $Row[0];
        $Data{Name}       = $Row[1];
        $Data{ValidID}    = $Row[2];
        $Data{Comment}    = $Row[3];
        $Data{CreateTime} = $Row[4];
        $Data{CreateBy}   = $Row[5];
        $Data{ChangeTime} = $Row[6];
        $Data{ChangeBy}   = $Row[7];
    }

    return if !%Data;

    # get notification event item data
    $DBObject->Prepare(
        SQL => '
            SELECT event_key, event_value
            FROM notification_event_item
            WHERE notification_id = ?
            ORDER BY event_key, event_value ASC',
        Bind => [ \$Data{ID} ],
    );

    while ( my @Row = $DBObject->FetchrowArray() ) {
        push @{ $Data{Data}->{ $Row[0] } }, $Row[1];
    }

    # get notification event message data
    $DBObject->Prepare(
        SQL => '
            SELECT subject, text, content_type, language
            FROM notification_event_message
            WHERE notification_id = ?',
        Bind => [ \$Data{ID} ],
    );

    while ( my @Row = $DBObject->FetchrowArray() ) {

        # add to message hash with the language as key
        $Data{Message}->{ $Row[3] } = {
            Subject     => $Row[0],
            Body        => $Row[1],
            ContentType => $Row[2],
        };
    }

    return %Data;
}

=head2 NotificationAdd()

adds a new notification to the database

    my $ID = $NotificationEventObject->NotificationAdd(
        Name => 'Agent::OwnerUpdate',
        Data => {
            Events => [ 'TicketQueueUpdate' ],
            ...
            Queue => [ 'SomeQueue' ],
        },
        Message => {
            en => {
                Subject     => 'Hello',
                Body        => 'Hello World',
                ContentType => 'text/plain',
            },
            de => {
                Subject     => 'Hallo',
                Body        => 'Hallo Welt',
                ContentType => 'text/plain',
            },
        },
        Comment => 'An optional comment', # (optional)
        ValidID => 1,
        UserID  => 123,
    );

=cut

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

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

    # check if job name already exists
    my %Check = $Self->NotificationGet(
        Name => $Param{Name},
    );
    if (%Check) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "A notification with the name '$Param{Name}' already exists.",
        );
        return;
    }

    # check message parameter
    if ( !IsHashRefWithData( $Param{Message} ) ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need Message!",
        );
        return;
    }

    # check each argument for each message language
    for my $Language ( sort keys %{ $Param{Message} } ) {

        for my $Argument (qw(Subject Body ContentType)) {

            # error if message data is incomplete
            if ( !$Param{Message}->{$Language}->{$Argument} ) {
                $Kernel::OM->Get('Kernel::System::Log')->Log(
                    Priority => 'error',
                    Message  => "Need Message argument '$Argument' for language '$Language'!",
                );
                return;
            }

            # fix some bad stuff from some browsers (Opera)!
            $Param{Message}->{$Language}->{Body} =~ s/(\n\r|\r\r\n|\r\n|\r)/\n/g;
        }
    }

    # get database object
    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');

    # insert data into db
    return if !$DBObject->Do(
        SQL => '
            INSERT INTO notification_event
                (name, valid_id, comments, create_time, create_by, change_time, change_by)
            VALUES (?, ?, ?, current_timestamp, ?, current_timestamp, ?)',
        Bind => [
            \$Param{Name}, \$Param{ValidID}, \$Param{Comment},
            \$Param{UserID}, \$Param{UserID},
        ],
    );

    # get id
    $DBObject->Prepare(
        SQL  => 'SELECT id FROM notification_event WHERE name = ?',
        Bind => [ \$Param{Name} ],
    );

    my $ID;
    while ( my @Row = $DBObject->FetchrowArray() ) {
        $ID = $Row[0];
    }

    # error handling
    if ( !$ID ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Could not get ID for just added notification '$Param{Name}'!",
        );
        return;
    }

    # insert notification event item data
    for my $Key ( sort keys %{ $Param{Data} } ) {

        ITEM:
        for my $Item ( @{ $Param{Data}->{$Key} } ) {

            next ITEM if !defined $Item;
            next ITEM if $Item eq '';

            return if !$DBObject->Do(
                SQL => '
                    INSERT INTO notification_event_item
                        (notification_id, event_key, event_value)
                    VALUES (?, ?, ?)',
                Bind => [ \$ID, \$Key, \$Item ],
            );
        }
    }

    # insert notification event message data
    for my $Language ( sort keys %{ $Param{Message} } ) {

        my %Message = %{ $Param{Message}->{$Language} };

        return if !$DBObject->Do(
            SQL => '
                INSERT INTO notification_event_message
                    (notification_id, subject, text, content_type, language)
                VALUES (?, ?, ?, ?, ?)',
            Bind => [
                \$ID,
                \$Message{Subject},
                \$Message{Body},
                \$Message{ContentType},
                \$Language,
            ],
        );
    }

    $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
        Type => $Self->{CacheType},
    );

    return $ID;
}

=head2 NotificationUpdate()

update a notification in database

    my $Ok = $NotificationEventObject->NotificationUpdate(
        ID      => 123,
        Name    => 'Agent::OwnerUpdate',
        Data => {
            Events => [ 'TicketQueueUpdate' ],
            ...
            Queue => [ 'SomeQueue' ],
        },
        Message => {
            en => {
                Subject     => 'Hello',
                Body        => 'Hello World',
                ContentType => 'text/plain',
            },
            de => {
                Subject     => 'Hallo',
                Body        => 'Hallo Welt',
                ContentType => 'text/plain',
            },
        },
        Comment => 'An optional comment',  # (optional)
        ValidID => 1,
        UserID  => 123,
    );

=cut

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

    # check needed stuff
    for my $Argument (qw(ID Name Data Message ValidID UserID)) {
        if ( !$Param{$Argument} ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $Argument!",
            );
            return;
        }
    }

    # check message parameter
    if ( !IsHashRefWithData( $Param{Message} ) ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need Message!",
        );
        return;
    }

    # check each argument for each message language
    for my $Language ( sort keys %{ $Param{Message} } ) {

        for my $Argument (qw(Subject Body ContentType)) {

            # error if message data is incomplete
            if ( !$Param{Message}->{$Language}->{$Argument} ) {
                $Kernel::OM->Get('Kernel::System::Log')->Log(
                    Priority => 'error',
                    Message  => "Need Message argument '$Argument' for language '$Language'!",
                );
                return;
            }

            # fix some bad stuff from some browsers (Opera)!
            $Param{Message}->{$Language}->{Body} =~ s/(\n\r|\r\r\n|\r\n|\r)/\n/g;
        }
    }

    # get database object
    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');

    # update data in db
    return if !$DBObject->Do(
        SQL => '
            UPDATE notification_event
            SET name = ?, valid_id = ?, comments = ?, change_time = current_timestamp, change_by = ?
            WHERE id = ?',
        Bind => [
            \$Param{Name},    \$Param{ValidID},
            \$Param{Comment}, \$Param{UserID},
            \$Param{ID},
        ],
    );

    # delete existing notification event item data
    $DBObject->Do(
        SQL  => 'DELETE FROM notification_event_item WHERE notification_id = ?',
        Bind => [ \$Param{ID} ],
    );

    # add new notification event item data
    for my $Key ( sort keys %{ $Param{Data} } ) {

        ITEM:
        for my $Item ( @{ $Param{Data}->{$Key} } ) {

            next ITEM if !defined $Item;
            next ITEM if $Item eq '';

            $DBObject->Do(
                SQL => '
                    INSERT INTO notification_event_item
                        (notification_id, event_key, event_value)
                    VALUES (?, ?, ?)',
                Bind => [
                    \$Param{ID},
                    \$Key,
                    \$Item,
                ],
            );
        }
    }

    # delete existing notification event message data
    $DBObject->Do(
        SQL  => 'DELETE FROM notification_event_message WHERE notification_id = ?',
        Bind => [ \$Param{ID} ],
    );

    # insert new notification event message data
    for my $Language ( sort keys %{ $Param{Message} } ) {

        my %Message = %{ $Param{Message}->{$Language} };

        $DBObject->Do(
            SQL => '
                INSERT INTO notification_event_message
                    (notification_id, subject, text, content_type, language)
                VALUES (?, ?, ?, ?, ?)',
            Bind => [
                \$Param{ID},
                \$Message{Subject},
                \$Message{Body},
                \$Message{ContentType},
                \$Language,
            ],
        );
    }

    $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
        Type => $Self->{CacheType},
    );

    return 1;
}

=head2 NotificationDelete()

deletes an notification from the database

    $NotificationEventObject->NotificationDelete(
        ID     => 1,
        UserID => 123,
    );

=cut

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

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

    # check if job name exists
    my %Check = $Self->NotificationGet(
        ID => $Param{ID},
    );
    if ( !%Check ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Can't delete notification with ID '$Param{ID}'. Notification does not exist!",
        );
        return;
    }

    # get database object
    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');

    # delete notification event item
    my $DeleteOK = $DBObject->Do(
        SQL  => 'DELETE FROM notification_event_item WHERE notification_id = ?',
        Bind => [ \$Param{ID} ],
    );

    # error handling
    if ( !$DeleteOK ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Can't delete notification_event_item with ID '$Param{ID}'!",
        );
        return;
    }

    # delete notification event message
    $DeleteOK = $DBObject->Do(
        SQL  => 'DELETE FROM notification_event_message WHERE notification_id = ?',
        Bind => [ \$Param{ID} ],
    );

    # error handling
    if ( !$DeleteOK ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Can't delete notification_event_message with ID '$Param{ID}'!",
        );
        return;
    }

    # delete notification event
    $DeleteOK = $DBObject->Do(
        SQL  => 'DELETE FROM notification_event WHERE id = ?',
        Bind => [ \$Param{ID} ],
    );

    # error handling
    if ( !$DeleteOK ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Can't delete notification_event with ID '$Param{ID}'!",
        );
        return;
    }

    $Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
        Type => $Self->{CacheType},
    );

    # success
    $Kernel::OM->Get('Kernel::System::Log')->Log(
        Priority => 'notice',
        Message  => "NotificationEvent notification '$Check{Name}' deleted (UserID=$Param{UserID}).",
    );

    return 1;
}

=head2 NotificationEventCheck()

returns array of notification affected by event

    my @IDs = $NotificationEventObject->NotificationEventCheck(
        Event => 'ArticleCreate',
    );

=cut

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

    # check needed stuff
    if ( !$Param{Event} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => 'Need Name!',
        );
        return;
    }

    # get needed objects
    my $DBObject    = $Kernel::OM->Get('Kernel::System::DB');
    my $ValidObject = $Kernel::OM->Get('Kernel::System::Valid');

    my @ValidIDs = $ValidObject->ValidIDsGet();
    my $ValidIDString = join ', ', @ValidIDs;

    $DBObject->Prepare(
        SQL => "
            SELECT DISTINCT(nei.notification_id)
            FROM notification_event ne, notification_event_item nei
            WHERE ne.id = nei.notification_id
                AND ne.valid_id IN ( $ValidIDString )
                AND nei.event_key = 'Events'
                AND nei.event_value = ?
            ORDER BY nei.notification_id ASC",
        Bind => [ \$Param{Event} ],
    );

    my @IDs;
    while ( my @Row = $DBObject->FetchrowArray() ) {
        push @IDs, $Row[0];
    }

    return @IDs;
}

=head2 NotificationImport()

import an Notification YAML file/content

    my $NotificationImport = $NotificationEventObject->NotificationImport(
        Content                   => $YAMLContent, # mandatory, YAML format
        OverwriteExistingNotifications => 0,            # 0 || 1
        UserID                    => 1,            # mandatory
    );

Returns:

    $NotificationImport = {
        Success      => 1,                         # 1 if success or undef if operation could not
                                                   #    be performed
        Message     => 'The Message to show.',     # error message
        AddedNotifications   => 'Notification1, Notification2',               # list of Notifications correctly added
        UpdatedNotifications => 'Notification3, Notification4',               # list of Notifications correctly updated
        NotificationErrors   => 'Notification5',                     # list of Notifications that could not be added or updated
    };

=cut

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

    for my $Needed (qw(Content UserID)) {

        # check needed stuff
        if ( !$Param{$Needed} ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $Needed!",
            );
            return {
                Success => 0,
                Message => "$Needed is missing can not continue.",
            };
        }
    }

    my $NotificationData = $Kernel::OM->Get('Kernel::System::YAML')->Load(
        Data => $Param{Content},
    );

    if ( ref $NotificationData ne 'ARRAY' ) {
        return {
            Success => 0,
            Message =>
                Translatable("Couldn't read Notification configuration file. Please make sure the file is valid."),
        };
    }

    # Check notification message length for every language.
    for my $Language ( sort keys %{ $NotificationData->[0]->{Message} } ) {
        my $Check = $Self->NotificationBodyCheck(
            Content => $NotificationData->[0]->{Message}->{$Language}->{Body},
            UserID  => $Param{UserID},
        );

        if ( !$Check ) {
            return {
                Success => 0,
                Message =>
                    Translatable('Imported notification has body text with more than 4000 characters.'),
            };
        }
    }

    my @UpdatedNotifications;
    my @AddedNotifications;
    my @NotificationErrors;

    my %CurrentNotifications = $Self->NotificationList(
        %Param,
        UserID => $Param{UserID},
    );
    my %ReverseCurrentNotifications = reverse %CurrentNotifications;

    Notification:
    for my $Notification ( @{$NotificationData} ) {

        next Notification if !$Notification;
        next Notification if ref $Notification ne 'HASH';

        if ( $Param{OverwriteExistingNotifications} && $ReverseCurrentNotifications{ $Notification->{Name} } ) {
            my $Success = $Self->NotificationUpdate(
                %{$Notification},
                ID     => $ReverseCurrentNotifications{ $Notification->{Name} },
                UserID => $Param{UserID},
            );

            if ($Success) {
                push @UpdatedNotifications, $Notification->{Name};
            }
            else {
                push @NotificationErrors, $Notification->{Name};
            }
        }
        else {

            # now add the Notification
            my $Success = $Self->NotificationAdd(
                %{$Notification},
                UserID => $Param{UserID},
            );

            if ($Success) {
                push @AddedNotifications, $Notification->{Name};
            }
            else {
                push @NotificationErrors, $Notification->{Name};
            }
        }
    }

    return {
        Success              => 1,
        AddedNotifications   => join( ', ', @AddedNotifications ) || '',
        UpdatedNotifications => join( ', ', @UpdatedNotifications ) || '',
        NotificationErrors   => join( ', ', @NotificationErrors ) || '',
    };
}

=head2 NotificationBodyCheck()

Check if body has a proper length depending on DB type.

    my $Ok = $NotificationEventObject->NotificationBodyCheck(
        Content => $BodyContent, # mandatory
        UserID  => 1,            # mandatory
    );

=cut

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

    # Check needed stuff.
    if ( !$Param{Content} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need Content!",
        );
        return;
    }

    my $DBType = $Kernel::OM->Get('Kernel::System::DB')->{'DB::Type'};

    # Body field length in the database is strictly set to 4000 characters for both PostgreSQL and Oracle backends.
    #   Since this restriction was not enforced for MySQL previously, it was possible to enter longer texts in the
    #   table. Because of this, we must now for reasons on backwards compatibility limit the body size only for those
    #   backends, at least until the next major version and planned field size change.
    #   Please see both bug#12843 (original semi-reverted fix) and bug#13281 for more information.
    if (
        (
            $DBType eq 'postgresql'
            || $DBType eq 'oracle'
        )
        && length $Param{Content} > 4000
        )
    {
        return 0;
    }

    return 1;
}

1;

=head1 TERMS AND CONDITIONS

This software is part of the OTRS project (L<http://otrs.org/>).

This software comes with ABSOLUTELY NO WARRANTY. For details, see
the enclosed file COPYING for license information (AGPL). If you
did not receive this file, see L<http://www.gnu.org/licenses/agpl.txt>.

=cut
