1252 lines
44 KiB
Perl
1252 lines
44 KiB
Perl
# --
|
|
# 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::Ticket::Event::NotificationEvent;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use List::Util qw(first);
|
|
use Mail::Address;
|
|
|
|
use Kernel::System::VariableCheck qw(:all);
|
|
|
|
our @ObjectDependencies = (
|
|
'Kernel::Config',
|
|
'Kernel::System::CustomerUser',
|
|
'Kernel::System::CheckItem',
|
|
'Kernel::System::DB',
|
|
'Kernel::System::DynamicField',
|
|
'Kernel::System::DynamicField::Backend',
|
|
'Kernel::System::Email',
|
|
'Kernel::System::Group',
|
|
'Kernel::System::HTMLUtils',
|
|
'Kernel::System::JSON',
|
|
'Kernel::System::Log',
|
|
'Kernel::System::NotificationEvent',
|
|
'Kernel::System::Queue',
|
|
'Kernel::System::SystemAddress',
|
|
'Kernel::System::TemplateGenerator',
|
|
'Kernel::System::Ticket',
|
|
'Kernel::System::Ticket::Article',
|
|
'Kernel::System::DateTime',
|
|
'Kernel::System::User',
|
|
'Kernel::System::CheckItem',
|
|
);
|
|
|
|
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} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !$Param{Data}->{TicketID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need TicketID in Data!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get ticket object
|
|
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
|
|
|
# Loop protection: prevent from running if ArticleSend has already triggered for certain ticket.
|
|
if ( $Param{Event} eq 'ArticleSend' ) {
|
|
return if $TicketObject->{'_NotificationEvent::ArticleSend'}->{ $Param{Data}->{TicketID} }++;
|
|
}
|
|
|
|
# return if no notification is active
|
|
return 1 if $TicketObject->{SendNoNotification};
|
|
|
|
# return if no ticket exists (e. g. it got deleted)
|
|
my $TicketExists = $TicketObject->TicketNumberLookup(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
return 1 if !$TicketExists;
|
|
|
|
# get notification event object
|
|
my $NotificationEventObject = $Kernel::OM->Get('Kernel::System::NotificationEvent');
|
|
|
|
# check if event is affected
|
|
my @IDs = $NotificationEventObject->NotificationEventCheck(
|
|
Event => $Param{Event},
|
|
);
|
|
|
|
# return if no notification for event exists
|
|
return 1 if !@IDs;
|
|
|
|
# get ticket attribute matches
|
|
my %Ticket = $TicketObject->TicketGet(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
UserID => $Param{UserID},
|
|
DynamicFields => 1,
|
|
);
|
|
|
|
# get dynamic field objects
|
|
my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
|
|
|
|
# get dynamic fields
|
|
my $DynamicFieldList = $DynamicFieldObject->DynamicFieldListGet(
|
|
Valid => 1,
|
|
ObjectType => ['Ticket'],
|
|
);
|
|
|
|
# create a dynamic field config lookup table
|
|
my %DynamicFieldConfigLookup;
|
|
for my $DynamicFieldConfig ( @{$DynamicFieldList} ) {
|
|
$DynamicFieldConfigLookup{ $DynamicFieldConfig->{Name} } = $DynamicFieldConfig;
|
|
}
|
|
|
|
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
|
|
|
NOTIFICATION:
|
|
for my $ID (@IDs) {
|
|
|
|
my %Notification = $NotificationEventObject->NotificationGet(
|
|
ID => $ID,
|
|
);
|
|
|
|
# verify ticket and article conditions
|
|
my $PassFilter = $Self->_NotificationFilter(
|
|
%Param,
|
|
Ticket => \%Ticket,
|
|
Notification => \%Notification,
|
|
DynamicFieldConfigLookup => \%DynamicFieldConfigLookup,
|
|
);
|
|
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] ) {
|
|
|
|
my $BackendObject = $ArticleObject->BackendForArticle(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
ArticleID => $Param{Data}->{ArticleID},
|
|
);
|
|
|
|
my %Index = $BackendObject->ArticleAttachmentIndex(
|
|
ArticleID => $Param{Data}->{ArticleID},
|
|
ExcludePlainText => 1,
|
|
ExcludeHTMLBody => 1,
|
|
);
|
|
if (%Index) {
|
|
FILE_ID:
|
|
for my $FileID ( sort keys %Index ) {
|
|
my %Attachment = $BackendObject->ArticleAttachment(
|
|
ArticleID => $Param{Data}->{ArticleID},
|
|
FileID => $FileID,
|
|
);
|
|
next FILE_ID if !%Attachment;
|
|
push @Attachments, \%Attachment;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# get recipients
|
|
my @RecipientUsers = $Self->_RecipientsGet(
|
|
%Param,
|
|
Ticket => \%Ticket,
|
|
Notification => \%Notification,
|
|
);
|
|
|
|
my @NotificationBundle;
|
|
|
|
# get template generator object
|
|
my $TemplateGeneratorObject = $Kernel::OM->Get('Kernel::System::TemplateGenerator');
|
|
|
|
# parse all notification tags for each user
|
|
for my $Recipient (@RecipientUsers) {
|
|
|
|
my %ReplacedNotification = $TemplateGeneratorObject->NotificationEvent(
|
|
TicketData => \%Ticket,
|
|
Recipient => $Recipient,
|
|
Notification => \%Notification,
|
|
CustomerMessageParams => $Param{Data}->{CustomerMessageParams},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
my $UserNotificationTransport = $Kernel::OM->Get('Kernel::System::JSON')->Decode(
|
|
Data => $Recipient->{NotificationTransport},
|
|
);
|
|
|
|
push @NotificationBundle, {
|
|
Recipient => $Recipient,
|
|
Notification => \%ReplacedNotification,
|
|
RecipientNotificationTransport => $UserNotificationTransport,
|
|
};
|
|
}
|
|
|
|
# get config object
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# get notification transport config
|
|
my %TransportConfig = %{ $ConfigObject->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('Kernel::System::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('Kernel::System::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 disable 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 send to the customer.
|
|
if (
|
|
$Bundle->{Recipient}->{Type} eq 'Customer'
|
|
&& $ConfigObject->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},
|
|
Notification => $Bundle->{Notification},
|
|
CustomerMessageParams => $Param{Data}->{CustomerMessageParams} || {},
|
|
Recipient => $Bundle->{Recipient},
|
|
Event => $Param{Event},
|
|
Attachments => \@Attachments,
|
|
Transport => $Transport,
|
|
TransportObject => $TransportObject,
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# 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,
|
|
Ticket => \%Ticket,
|
|
);
|
|
|
|
next TRANSPORT if !@TransportRecipients;
|
|
|
|
RECIPIENT:
|
|
for my $Recipient (@TransportRecipients) {
|
|
|
|
# replace all notification tags for each special recipient
|
|
my %ReplacedNotification = $TemplateGeneratorObject->NotificationEvent(
|
|
TicketData => \%Ticket,
|
|
Recipient => $Recipient,
|
|
Notification => \%Notification,
|
|
CustomerMessageParams => $Param{Data}->{CustomerMessageParams} || {},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
my $Success = $Self->_SendRecipientNotification(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
Notification => \%ReplacedNotification,
|
|
CustomerMessageParams => $Param{Data}->{CustomerMessageParams} || {},
|
|
Recipient => $Recipient,
|
|
Event => $Param{Event},
|
|
Attachments => \@Attachments,
|
|
Transport => $Transport,
|
|
TransportObject => $TransportObject,
|
|
UserID => $Param{UserID},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub _NotificationFilter {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed params
|
|
for my $Needed (qw(Ticket Notification DynamicFieldConfigLookup)) {
|
|
return if !$Param{$Needed};
|
|
}
|
|
|
|
# set local values
|
|
my %Notification = %{ $Param{Notification} };
|
|
|
|
# get dynamic field backend object
|
|
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
|
|
|
|
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
|
|
|
# get the search article fields to retrieve values for
|
|
my %ArticleSearchableFields = $ArticleObject->ArticleSearchableFieldsList();
|
|
|
|
KEY:
|
|
for my $Key ( sort keys %{ $Notification{Data} } ) {
|
|
|
|
# TODO: This function here should be fixed to not use hardcoded attribute values!
|
|
# ignore not ticket related attributes
|
|
next KEY if $Key eq 'Recipients';
|
|
next KEY if $Key eq 'SkipRecipients';
|
|
next KEY if $Key eq 'RecipientAgents';
|
|
next KEY if $Key eq 'RecipientGroups';
|
|
next KEY if $Key eq 'RecipientRoles';
|
|
next KEY if $Key eq 'TransportEmailTemplate';
|
|
next KEY if $Key eq 'Events';
|
|
next KEY if $Key eq 'ArticleSenderTypeID';
|
|
next KEY if $Key eq 'ArticleIsVisibleForCustomer';
|
|
next KEY if $Key eq 'ArticleCommunicationChannelID';
|
|
next KEY if $Key eq 'ArticleAttachmentInclude';
|
|
next KEY if $Key eq 'IsVisibleForCustomer';
|
|
next KEY if $Key eq 'Transports';
|
|
next KEY if $Key eq 'OncePerDay';
|
|
next KEY if $Key eq 'VisibleForAgent';
|
|
next KEY if $Key eq 'VisibleForAgentTooltip';
|
|
next KEY if $Key eq 'LanguageID';
|
|
next KEY if $Key eq 'SendOnOutOfOffice';
|
|
next KEY if $Key eq 'AgentEnabledByDefault';
|
|
next KEY if $Key eq 'EmailSecuritySettings';
|
|
next KEY if $Key eq 'EmailSigningCrypting';
|
|
next KEY if $Key eq 'EmailMissingCryptingKeys';
|
|
next KEY if $Key eq 'EmailMissingSigningKeys';
|
|
next KEY if $Key eq 'EmailDefaultSigningKeys';
|
|
next KEY if $Key eq 'NotificationType';
|
|
|
|
# ignore article searchable fields
|
|
next KEY if $ArticleSearchableFields{$Key};
|
|
|
|
# skip transport related attributes
|
|
if ( $Key =~ m{ \A ( Recipient | Transport ) }xms ) {
|
|
next KEY;
|
|
}
|
|
|
|
# check ticket attributes
|
|
next KEY if !defined $Notification{Data}->{$Key};
|
|
next KEY if !defined $Notification{Data}->{$Key}->[0];
|
|
next KEY if !@{ $Notification{Data}->{$Key} };
|
|
my $Match = 0;
|
|
|
|
VALUE:
|
|
for my $Value ( @{ $Notification{Data}->{$Key} } ) {
|
|
|
|
next VALUE if !defined $Value;
|
|
|
|
# check if key is a search dynamic field
|
|
if ( $Key =~ m{\A Search_DynamicField_}xms ) {
|
|
|
|
# remove search prefix
|
|
my $DynamicFieldName = $Key;
|
|
|
|
$DynamicFieldName =~ s{Search_DynamicField_}{};
|
|
|
|
# get the dynamic field config for this field
|
|
my $DynamicFieldConfig = $Param{DynamicFieldConfigLookup}->{$DynamicFieldName};
|
|
|
|
next VALUE if !$DynamicFieldConfig;
|
|
|
|
my $IsNotificationEventCondition = $DynamicFieldBackendObject->HasBehavior(
|
|
DynamicFieldConfig => $DynamicFieldConfig,
|
|
Behavior => 'IsNotificationEventCondition',
|
|
);
|
|
|
|
next VALUE if !$IsNotificationEventCondition;
|
|
|
|
# Get match value from the dynamic field backend, if applicable (bug#12257).
|
|
my $MatchValue;
|
|
my $SearchFieldParameter = $DynamicFieldBackendObject->SearchFieldParameterBuild(
|
|
DynamicFieldConfig => $DynamicFieldConfig,
|
|
Profile => {
|
|
$Key => $Value,
|
|
},
|
|
);
|
|
if ( defined $SearchFieldParameter->{Parameter}->{Equals} ) {
|
|
$MatchValue = $SearchFieldParameter->{Parameter}->{Equals};
|
|
}
|
|
else {
|
|
$MatchValue = $Value;
|
|
}
|
|
|
|
$Match = $DynamicFieldBackendObject->ObjectMatch(
|
|
DynamicFieldConfig => $DynamicFieldConfig,
|
|
Value => $MatchValue,
|
|
ObjectAttributes => $Param{Ticket},
|
|
);
|
|
|
|
last VALUE if $Match;
|
|
}
|
|
else {
|
|
|
|
if (
|
|
$Param{Ticket}->{$Key}
|
|
&& $Value eq $Param{Ticket}->{$Key}
|
|
)
|
|
{
|
|
$Match = 1;
|
|
last VALUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return if !$Match;
|
|
}
|
|
|
|
# match article types only on ArticleCreate or ArticleSend event
|
|
if (
|
|
( ( $Param{Event} eq 'ArticleCreate' ) || ( $Param{Event} eq 'ArticleSend' ) )
|
|
&& $Param{Data}->{ArticleID}
|
|
)
|
|
{
|
|
my $BackendObject = $ArticleObject->BackendForArticle(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
ArticleID => $Param{Data}->{ArticleID},
|
|
);
|
|
|
|
my %Article = $BackendObject->ArticleGet(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
ArticleID => $Param{Data}->{ArticleID},
|
|
DynamicFields => 0,
|
|
);
|
|
|
|
# Check for active article filters:
|
|
# - SenderTypeID
|
|
# - IsVisibleForCustomer
|
|
# - CommunicationChannelID
|
|
ARTICLE_FILTER:
|
|
for my $ArticleFilter (qw(ArticleSenderTypeID ArticleIsVisibleForCustomer ArticleCommunicationChannelID)) {
|
|
next ARTICLE_FILTER if !$Notification{Data}->{$ArticleFilter};
|
|
|
|
my $Match = 0;
|
|
VALUE:
|
|
for my $Value ( @{ $Notification{Data}->{$ArticleFilter} } ) {
|
|
next VALUE if !defined $Value;
|
|
|
|
my $ArticleField = $ArticleFilter;
|
|
$ArticleField =~ s/^Article//;
|
|
|
|
if ( $Value == $Article{$ArticleField} ) {
|
|
$Match = 1;
|
|
last VALUE;
|
|
}
|
|
}
|
|
|
|
return if !$Match;
|
|
}
|
|
|
|
my %ArticleData = $BackendObject->ArticleSearchableContentGet(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
ArticleID => $Param{Data}->{ArticleID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# check article backend fields
|
|
KEY:
|
|
for my $Key ( sort keys %ArticleSearchableFields ) {
|
|
|
|
next KEY if !$Notification{Data}->{$Key};
|
|
|
|
my $Match = 0;
|
|
VALUE:
|
|
for my $Value ( @{ $Notification{Data}->{$Key} } ) {
|
|
|
|
next VALUE if !$Value;
|
|
|
|
if ( $ArticleData{$Key}->{String} =~ /\Q$Value\E/i ) {
|
|
$Match = 1;
|
|
last VALUE;
|
|
}
|
|
}
|
|
|
|
return if !$Match;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
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} };
|
|
|
|
# get needed objects
|
|
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
|
my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
my @RecipientUserIDs;
|
|
my @RecipientUsers;
|
|
my @RecipientUserEmails;
|
|
|
|
# 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;
|
|
|
|
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
|
|
|
# get recipients by Recipients
|
|
if ( $Notification{Data}->{Recipients} ) {
|
|
|
|
# get needed objects
|
|
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
|
|
my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser');
|
|
my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem');
|
|
my $SystemAddressObject = $Kernel::OM->Get('Kernel::System::SystemAddress');
|
|
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
|
|
|
RECIPIENT:
|
|
for my $Recipient ( @{ $Notification{Data}->{Recipients} } ) {
|
|
|
|
if (
|
|
$Recipient
|
|
=~ /^Agent(Owner|Responsible|Watcher|WritePermissions|MyQueues|MyServices|MyQueuesMyServices|CreateBy)$/
|
|
)
|
|
{
|
|
if ( $Recipient eq 'AgentOwner' ) {
|
|
push @{ $Notification{Data}->{RecipientAgents} }, $Ticket{OwnerID};
|
|
}
|
|
elsif ( $Recipient eq 'AgentResponsible' ) {
|
|
|
|
# add the responsible agent to the notification list
|
|
if ( $ConfigObject->Get('Ticket::Responsible') && $Ticket{ResponsibleID} ) {
|
|
|
|
push @{ $Notification{Data}->{RecipientAgents} },
|
|
$Ticket{ResponsibleID};
|
|
}
|
|
}
|
|
elsif ( $Recipient eq 'AgentWatcher' ) {
|
|
|
|
# is not needed to check Ticket::Watcher,
|
|
# its checked on TicketWatchGet function
|
|
push @{ $Notification{Data}->{RecipientAgents} }, $TicketObject->TicketWatchGet(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
Result => 'ARRAY',
|
|
);
|
|
}
|
|
elsif ( $Recipient eq 'AgentWritePermissions' ) {
|
|
|
|
my $GroupID = $QueueObject->GetQueueGroupID(
|
|
QueueID => $Ticket{QueueID},
|
|
);
|
|
|
|
my %UserList = $GroupObject->PermissionGroupUserGet(
|
|
GroupID => $GroupID,
|
|
Type => 'rw',
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
my %RoleList = $GroupObject->PermissionGroupRoleGet(
|
|
GroupID => $GroupID,
|
|
Type => 'rw',
|
|
);
|
|
for my $RoleID ( sort keys %RoleList ) {
|
|
my %RoleUserList = $GroupObject->PermissionRoleUserGet(
|
|
RoleID => $RoleID,
|
|
);
|
|
%UserList = ( %RoleUserList, %UserList );
|
|
}
|
|
|
|
my @UserIDs = sort keys %UserList;
|
|
|
|
push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
|
|
}
|
|
elsif ( $Recipient eq 'AgentMyQueues' ) {
|
|
|
|
# get subscribed users
|
|
my %MyQueuesUserIDs = map { $_ => 1 } $TicketObject->GetSubscribedUserIDsByQueueID(
|
|
QueueID => $Ticket{QueueID}
|
|
);
|
|
|
|
my @UserIDs = sort keys %MyQueuesUserIDs;
|
|
|
|
push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
|
|
}
|
|
elsif ( $Recipient eq 'AgentMyServices' ) {
|
|
|
|
# get subscribed users
|
|
my %MyServicesUserIDs;
|
|
if ( $Ticket{ServiceID} ) {
|
|
%MyServicesUserIDs = map { $_ => 1 } $TicketObject->GetSubscribedUserIDsByServiceID(
|
|
ServiceID => $Ticket{ServiceID},
|
|
);
|
|
}
|
|
|
|
my @UserIDs = sort keys %MyServicesUserIDs;
|
|
|
|
push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
|
|
}
|
|
elsif ( $Recipient eq 'AgentMyQueuesMyServices' ) {
|
|
|
|
# get subscribed users
|
|
my %MyQueuesUserIDs = map { $_ => 1 } $TicketObject->GetSubscribedUserIDsByQueueID(
|
|
QueueID => $Ticket{QueueID}
|
|
);
|
|
|
|
# get subscribed users
|
|
my %MyServicesUserIDs;
|
|
if ( $Ticket{ServiceID} ) {
|
|
%MyServicesUserIDs = map { $_ => 1 } $TicketObject->GetSubscribedUserIDsByServiceID(
|
|
ServiceID => $Ticket{ServiceID},
|
|
);
|
|
}
|
|
|
|
# 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};
|
|
}
|
|
}
|
|
|
|
my @UserIDs = sort keys %SubscribedUserIDs;
|
|
|
|
push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
|
|
}
|
|
elsif ( $Recipient eq 'AgentCreateBy' ) {
|
|
|
|
# Check if the first article was created by an agent.
|
|
my @Articles = $ArticleObject->ArticleList(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
SenderType => 'agent',
|
|
OnlyFirst => 1,
|
|
);
|
|
|
|
if ( $Articles[0] && $Articles[0]->{ArticleNumber} == 1 ) {
|
|
push @{ $Notification{Data}->{RecipientAgents} }, $Ticket{CreateBy};
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
# Other OTRS packages might add other kind of recipients that are normally handled by
|
|
# other modules then an elsif condition here is useful.
|
|
elsif ( $Recipient eq 'Customer' ) {
|
|
|
|
# Get last article from customer.
|
|
my @CustomerArticles = $ArticleObject->ArticleList(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
SenderType => 'customer',
|
|
OnlyLast => 1,
|
|
);
|
|
|
|
my %CustomerArticle;
|
|
|
|
ARTICLE:
|
|
for my $Article (@CustomerArticles) {
|
|
next ARTICLE if !$Article->{ArticleID};
|
|
|
|
%CustomerArticle = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet(
|
|
%{$Article},
|
|
DynamicFields => 0,
|
|
);
|
|
}
|
|
|
|
my %Article = %CustomerArticle;
|
|
|
|
# If the ticket has no customer article, get the last agent article.
|
|
if ( !%CustomerArticle ) {
|
|
|
|
# Get last article from agent.
|
|
my @AgentArticles = $ArticleObject->ArticleList(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
SenderType => 'agent',
|
|
OnlyLast => 1,
|
|
);
|
|
|
|
my %AgentArticle;
|
|
|
|
ARTICLE:
|
|
for my $Article (@AgentArticles) {
|
|
next ARTICLE if !$Article->{ArticleID};
|
|
|
|
%AgentArticle = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet(
|
|
%{$Article},
|
|
DynamicFields => 0,
|
|
);
|
|
}
|
|
|
|
%Article = %AgentArticle;
|
|
}
|
|
|
|
# Get raw ticket data.
|
|
my %Ticket = $TicketObject->TicketGet(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
DynamicFields => 0,
|
|
);
|
|
|
|
my %Recipient;
|
|
|
|
# When there is no customer article, last agent article will be used. In this case notification must not
|
|
# be sent to the "From", but to the "To" article field.
|
|
|
|
# Check if we actually do have an article.
|
|
if ( defined $Article{SenderType} ) {
|
|
if ( $Article{SenderType} eq 'customer' ) {
|
|
$Recipient{UserEmail} = $Article{From};
|
|
}
|
|
else {
|
|
$Recipient{UserEmail} = $Article{To};
|
|
}
|
|
}
|
|
$Recipient{Type} = 'Customer';
|
|
|
|
# check if customer notifications should be send
|
|
if (
|
|
$ConfigObject->Get('CustomerNotifyJustToRealCustomer')
|
|
&& !$Ticket{CustomerUserID}
|
|
)
|
|
{
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'info',
|
|
Message => 'Send no customer notification because no customer is set!',
|
|
);
|
|
next RECIPIENT;
|
|
}
|
|
|
|
# get language and send recipient
|
|
$Recipient{Language} = $ConfigObject->Get('DefaultLanguage') || 'en';
|
|
|
|
if ( $Ticket{CustomerUserID} ) {
|
|
|
|
my %CustomerUser = $CustomerUserObject->CustomerUserDataGet(
|
|
User => $Ticket{CustomerUserID},
|
|
|
|
);
|
|
|
|
# Check if customer user is email address, in case it is not stored in system
|
|
if (
|
|
!IsHashRefWithData( \%CustomerUser )
|
|
&& !$ConfigObject->Get('CustomerNotifyJustToRealCustomer')
|
|
&& $Kernel::OM->Get('Kernel::System::CheckItem')
|
|
->CheckEmail( Address => $Ticket{CustomerUserID} )
|
|
)
|
|
{
|
|
$Recipient{UserEmail} = $Ticket{CustomerUserID};
|
|
}
|
|
else {
|
|
|
|
# join Recipient data with CustomerUser data
|
|
%Recipient = ( %Recipient, %CustomerUser );
|
|
}
|
|
|
|
# get user language
|
|
if ( $CustomerUser{UserLanguage} ) {
|
|
$Recipient{Language} = $CustomerUser{UserLanguage};
|
|
}
|
|
}
|
|
|
|
# get real name
|
|
if ( $Ticket{CustomerUserID} ) {
|
|
$Recipient{Realname} = $CustomerUserObject->CustomerName(
|
|
UserLogin => $Ticket{CustomerUserID},
|
|
);
|
|
}
|
|
if ( !$Recipient{Realname} ) {
|
|
$Recipient{Realname} = $Article{From} || '';
|
|
$Recipient{Realname} =~ s/<.*>|\(.*\)|\"|;|,//g;
|
|
$Recipient{Realname} =~ s/( $)|( $)//g;
|
|
}
|
|
|
|
# Skip notification if email address is already used by other groups.
|
|
next RECIPIENT if grep { $_ eq $Recipient{UserEmail} } @RecipientUserEmails;
|
|
|
|
# Push Email Addresses into array to prevent multiple notifications.
|
|
push @RecipientUserEmails, $Recipient{UserEmail};
|
|
|
|
push @RecipientUsers, \%Recipient;
|
|
}
|
|
elsif ( $Recipient eq 'AllRecipientsFirstArticle' || $Recipient eq 'AllRecipientsLastArticle' ) {
|
|
|
|
my $SystemSenderType = $ArticleObject->ArticleSenderTypeLookup( SenderType => 'system' );
|
|
|
|
my %Article;
|
|
my @MetaArticles = grep { $_->{SenderTypeID} ne $SystemSenderType } $ArticleObject->ArticleList(
|
|
TicketID => $Param{Data}->{TicketID},
|
|
);
|
|
|
|
# Get the first or the last article.
|
|
if ( $Recipient eq 'AllRecipientsFirstArticle' ) {
|
|
@MetaArticles = splice @MetaArticles, 0, 1;
|
|
}
|
|
elsif ( $Recipient eq 'AllRecipientsLastArticle' ) {
|
|
@MetaArticles = splice @MetaArticles, -1, 1;
|
|
}
|
|
|
|
if (@MetaArticles) {
|
|
my $ArticleBackend = $ArticleObject->BackendForArticle( %{ $MetaArticles[0] } );
|
|
if ( $ArticleBackend->ChannelNameGet() ne 'Email' ) {
|
|
next RECIPIENT;
|
|
|
|
}
|
|
%Article = $ArticleBackend->ArticleGet(
|
|
%{ $MetaArticles[0] },
|
|
DynamicFields => 0,
|
|
);
|
|
}
|
|
|
|
if ( !%Article ) {
|
|
next RECIPIENT;
|
|
}
|
|
|
|
my %Recipient;
|
|
my @AllRecipients;
|
|
my @TmpRecipients;
|
|
my @TmpRecipientAgents;
|
|
my @RecipientAgents;
|
|
|
|
# Get recipient agents to prevent multiple notifications
|
|
if ( IsArrayRefWithData( $Notification{Data}->{RecipientAgents} ) ) {
|
|
@RecipientAgents = @{ $Notification{Data}->{RecipientAgents} };
|
|
}
|
|
|
|
if (@RecipientAgents) {
|
|
for my $UserID (@RecipientAgents) {
|
|
|
|
my %User = $UserObject->GetUserData(
|
|
UserID => $UserID,
|
|
);
|
|
|
|
push @TmpRecipientAgents, $User{UserEmail};
|
|
}
|
|
}
|
|
|
|
# Get all recipients from the article.
|
|
ALLRECIPIENTS:
|
|
for my $Header (qw(From To Cc)) {
|
|
|
|
next ALLRECIPIENTS if !$Article{$Header};
|
|
|
|
push @TmpRecipients, split ',', $Article{$Header};
|
|
}
|
|
|
|
# Loop through recipients.
|
|
EMAIL:
|
|
for my $Email ( Mail::Address->parse(@TmpRecipients) ) {
|
|
|
|
# Skip notification if email address is already used by other groups.
|
|
next EMAIL if grep { $_ eq $Email->address() } @RecipientUserEmails;
|
|
|
|
# Validate email address.
|
|
my $Valid = $CheckItemObject->CheckEmail(
|
|
Address => $Email->address(),
|
|
);
|
|
|
|
# Skip invalid.
|
|
next EMAIL if !$Valid;
|
|
|
|
# Check if email address is a local.
|
|
my $IsLocal = $SystemAddressObject->SystemAddressIsLocalAddress(
|
|
Address => $Email->address(),
|
|
);
|
|
|
|
# Skip local email address.
|
|
next EMAIL if $IsLocal;
|
|
|
|
# Skip email addresses from agents selected by other groups.
|
|
next EMAIL if grep { $_ eq $Email->address() } @TmpRecipientAgents;
|
|
|
|
push @AllRecipients, $Email->address();
|
|
|
|
# Push Email Addresses into array to prevent multiple notifications.
|
|
push @RecipientUserEmails, $Email->address();
|
|
}
|
|
|
|
# Merge recipients.
|
|
$Recipient{UserEmail} = join( ',', @AllRecipients );
|
|
|
|
$Recipient{Type} = 'Customer';
|
|
|
|
# Get user language.
|
|
$Recipient{Language} = $ConfigObject->Get('DefaultLanguage') || 'en';
|
|
|
|
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 RecipientGroups
|
|
if ( $Notification{Data}->{RecipientGroups} ) {
|
|
|
|
RECIPIENT:
|
|
for my $GroupID ( @{ $Notification{Data}->{RecipientGroups} } ) {
|
|
|
|
my %GroupMemberList = $GroupObject->PermissionGroupUserGet(
|
|
GroupID => $GroupID,
|
|
Type => 'ro',
|
|
);
|
|
|
|
GROUPMEMBER:
|
|
for my $UserID ( sort keys %GroupMemberList ) {
|
|
|
|
next GROUPMEMBER if $UserID == 1;
|
|
next GROUPMEMBER if $AgentUsed{$UserID};
|
|
|
|
$AgentUsed{$UserID} = 1;
|
|
|
|
push @RecipientUserIDs, $UserID;
|
|
}
|
|
}
|
|
}
|
|
|
|
# get recipients by RecipientRoles
|
|
if ( $Notification{Data}->{RecipientRoles} ) {
|
|
|
|
RECIPIENT:
|
|
for my $RoleID ( @{ $Notification{Data}->{RecipientRoles} } ) {
|
|
|
|
my %RoleMemberList = $GroupObject->PermissionRoleUserGet(
|
|
RoleID => $RoleID,
|
|
);
|
|
|
|
ROLEMEMBER:
|
|
for my $UserID ( sort keys %RoleMemberList ) {
|
|
|
|
next ROLEMEMBER if $UserID == 1;
|
|
next ROLEMEMBER if $AgentUsed{$UserID};
|
|
|
|
$AgentUsed{$UserID} = 1;
|
|
|
|
push @RecipientUserIDs, $UserID;
|
|
}
|
|
}
|
|
}
|
|
|
|
# get needed objects
|
|
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
|
|
|
my %SkipRecipients;
|
|
if ( IsArrayRefWithData( $Param{Data}->{SkipRecipients} ) ) {
|
|
%SkipRecipients = map { $_ => 1 } @{ $Param{Data}->{SkipRecipients} };
|
|
}
|
|
|
|
# agent 1 should not receive notifications
|
|
$SkipRecipients{'1'} = 1;
|
|
|
|
# remove recipients should not receive a notification
|
|
@RecipientUserIDs = grep { !$SkipRecipients{$_} } @RecipientUserIDs;
|
|
|
|
# get valid users list
|
|
my %ValidUsersList = $UserObject->UserList(
|
|
Type => 'Short',
|
|
Valid => 1,
|
|
NoOutOfOffice => 0,
|
|
);
|
|
|
|
# remove invalid users
|
|
@RecipientUserIDs = grep { $ValidUsersList{$_} } @RecipientUserIDs;
|
|
|
|
# remove duplicated
|
|
my %TempRecipientUserIDs = map { $_ => 1 } @RecipientUserIDs;
|
|
@RecipientUserIDs = sort keys %TempRecipientUserIDs;
|
|
|
|
# get time object
|
|
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
|
|
|
# get all data for recipients as they should be needed by all notification transports
|
|
RECIPIENT:
|
|
for my $UserID (@RecipientUserIDs) {
|
|
|
|
my %User = $UserObject->GetUserData(
|
|
UserID => $UserID,
|
|
Valid => 1,
|
|
);
|
|
next RECIPIENT if !%User;
|
|
|
|
# skip user that triggers the event (it should not be notified) but only if it is not
|
|
# a pre-calculated recipient
|
|
if (
|
|
!$ConfigObject->Get('AgentSelfNotifyOnAction')
|
|
&& $User{UserID} == $Param{UserID}
|
|
&& !$PrecalculatedUserIDs{ $Param{UserID} }
|
|
)
|
|
{
|
|
next RECIPIENT;
|
|
}
|
|
|
|
# skip users out of the office if configured
|
|
if ( !$Notification{Data}->{SendOnOutOfOffice} && $User{OutOfOffice} ) {
|
|
my $Start = sprintf(
|
|
"%04d-%02d-%02d 00:00:00",
|
|
$User{OutOfOfficeStartYear}, $User{OutOfOfficeStartMonth},
|
|
$User{OutOfOfficeStartDay}
|
|
);
|
|
my $TimeStart = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
String => $Start,
|
|
}
|
|
);
|
|
my $End = sprintf(
|
|
"%04d-%02d-%02d 23:59:59",
|
|
$User{OutOfOfficeEndYear}, $User{OutOfOfficeEndMonth},
|
|
$User{OutOfOfficeEndDay}
|
|
);
|
|
my $TimeEnd = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
String => $End,
|
|
}
|
|
);
|
|
|
|
next RECIPIENT if $TimeStart < $DateTimeObject && $TimeEnd > $DateTimeObject;
|
|
}
|
|
|
|
# skip users with out ro permissions
|
|
my $Permission = $TicketObject->TicketPermission(
|
|
Type => 'ro',
|
|
TicketID => $Ticket{TicketID},
|
|
UserID => $User{UserID}
|
|
);
|
|
|
|
next RECIPIENT if !$Permission;
|
|
|
|
# skip PostMasterUserID
|
|
my $PostmasterUserID = $ConfigObject->Get('PostmasterUserID') || 1;
|
|
next RECIPIENT if $User{UserID} == $PostmasterUserID;
|
|
|
|
$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('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
}
|
|
}
|
|
|
|
# get ticket object
|
|
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
|
|
|
# check if the notification needs to be sent just one time per day
|
|
if ( $Param{Notification}->{Data}->{OncePerDay} && $Param{Recipient}->{UserLogin} ) {
|
|
|
|
# get ticket history
|
|
my @HistoryLines = $TicketObject->HistoryGet(
|
|
TicketID => $Param{TicketID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# get last notification sent ticket history entry for this transport and this user
|
|
my $LastNotificationHistory;
|
|
if ( defined $Param{Recipient}->{Source} && $Param{Recipient}->{Source} eq 'CustomerUser' ) {
|
|
$LastNotificationHistory = first {
|
|
$_->{HistoryType} eq 'SendCustomerNotification'
|
|
&& $_->{Name} eq
|
|
"\%\%$Param{Recipient}->{UserEmail}"
|
|
}
|
|
reverse @HistoryLines;
|
|
}
|
|
else {
|
|
$LastNotificationHistory = first {
|
|
$_->{HistoryType} eq 'SendAgentNotification'
|
|
&& $_->{Name} eq
|
|
"\%\%$Param{Notification}->{Name}\%\%$Param{Recipient}->{UserLogin}\%\%$Param{Transport}"
|
|
}
|
|
reverse @HistoryLines;
|
|
}
|
|
|
|
if ( $LastNotificationHistory && $LastNotificationHistory->{CreateTime} ) {
|
|
|
|
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
|
|
|
my $LastNotificationDateTimeObject = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
String => $LastNotificationHistory->{CreateTime},
|
|
},
|
|
);
|
|
|
|
# do not send the notification if it has been sent already today
|
|
if (
|
|
$DateTimeObject->Format( Format => "%Y-%m-%d" ) eq
|
|
$LastNotificationDateTimeObject->Format( Format => "%Y-%m-%d" )
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
my $TransportObject = $Param{TransportObject};
|
|
|
|
# send notification to each recipient
|
|
my $Success = $TransportObject->SendNotification(
|
|
TicketID => $Param{TicketID},
|
|
UserID => $Param{UserID},
|
|
Notification => $Param{Notification},
|
|
CustomerMessageParams => $Param{CustomerMessageParams},
|
|
Recipient => $Param{Recipient},
|
|
Event => $Param{Event},
|
|
Attachments => $Param{Attachments},
|
|
);
|
|
|
|
return if !$Success;
|
|
|
|
if (
|
|
$Param{Recipient}->{Type} eq 'Agent'
|
|
&& $Param{Recipient}->{UserLogin}
|
|
)
|
|
{
|
|
|
|
# write history
|
|
$TicketObject->HistoryAdd(
|
|
TicketID => $Param{TicketID},
|
|
HistoryType => 'SendAgentNotification',
|
|
Name => "\%\%$Param{Notification}->{Name}\%\%$Param{Recipient}->{UserLogin}\%\%$Param{Transport}",
|
|
CreateUserID => $Param{UserID},
|
|
);
|
|
}
|
|
|
|
my %EventData = %{ $TransportObject->GetTransportEventData() };
|
|
|
|
return 1 if !%EventData;
|
|
|
|
if ( !$EventData{Event} || !$EventData{Data} || !$EventData{UserID} ) {
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Could not trigger notification post send event",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
# ticket event
|
|
$TicketObject->EventHandler(
|
|
%EventData,
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
1;
|