# -- # Copyright (C) 2001-2019 OTRS AG, https://otrs.com/ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you # did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. # -- 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 ValidID UserID)) { if ( !$Param{$Argument} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Argument!", ); return; } } # Check message parameter. if ( !$Param{PossibleEmptyMessage} && !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; } } 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). This software comes with ABSOLUTELY NO WARRANTY. For details, see the enclosed file COPYING for license information (GPL). If you did not receive this file, see L. =cut