1# --
2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
3# --
4# This software comes with ABSOLUTELY NO WARRANTY. For details, see
5# the enclosed file COPYING for license information (GPL). If you
6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
7# --
8
9package Kernel::System::Ticket::Event::NotificationEvent;
10
11use strict;
12use warnings;
13
14use List::Util qw(first);
15use Mail::Address;
16
17use Kernel::System::VariableCheck qw(:all);
18
19our @ObjectDependencies = (
20    'Kernel::Config',
21    'Kernel::System::CustomerUser',
22    'Kernel::System::CheckItem',
23    'Kernel::System::DB',
24    'Kernel::System::DynamicField',
25    'Kernel::System::DynamicField::Backend',
26    'Kernel::System::Email',
27    'Kernel::System::Group',
28    'Kernel::System::HTMLUtils',
29    'Kernel::System::JSON',
30    'Kernel::System::Log',
31    'Kernel::System::NotificationEvent',
32    'Kernel::System::Queue',
33    'Kernel::System::SystemAddress',
34    'Kernel::System::TemplateGenerator',
35    'Kernel::System::Ticket',
36    'Kernel::System::Ticket::Article',
37    'Kernel::System::DateTime',
38    'Kernel::System::User',
39    'Kernel::System::CheckItem',
40);
41
42sub new {
43    my ( $Type, %Param ) = @_;
44
45    # allocate new hash for object
46    my $Self = {};
47    bless( $Self, $Type );
48
49    return $Self;
50}
51
52sub Run {
53    my ( $Self, %Param ) = @_;
54
55    # check needed stuff
56    for my $Needed (qw(Event Data Config UserID)) {
57        if ( !$Param{$Needed} ) {
58            $Kernel::OM->Get('Kernel::System::Log')->Log(
59                Priority => 'error',
60                Message  => "Need $Needed!",
61            );
62            return;
63        }
64    }
65
66    if ( !$Param{Data}->{TicketID} ) {
67        $Kernel::OM->Get('Kernel::System::Log')->Log(
68            Priority => 'error',
69            Message  => 'Need TicketID in Data!',
70        );
71        return;
72    }
73
74    # get ticket object
75    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
76
77    # Loop protection: prevent from running if ArticleSend has already triggered for certain ticket.
78    if ( $Param{Event} eq 'ArticleSend' ) {
79        return if $TicketObject->{'_NotificationEvent::ArticleSend'}->{ $Param{Data}->{TicketID} }++;
80    }
81
82    # return if no notification is active
83    return 1 if $TicketObject->{SendNoNotification};
84
85    # return if no ticket exists (e. g. it got deleted)
86    my $TicketExists = $TicketObject->TicketNumberLookup(
87        TicketID => $Param{Data}->{TicketID},
88        UserID   => $Param{UserID},
89    );
90
91    return 1 if !$TicketExists;
92
93    # get notification event object
94    my $NotificationEventObject = $Kernel::OM->Get('Kernel::System::NotificationEvent');
95
96    # check if event is affected
97    my @IDs = $NotificationEventObject->NotificationEventCheck(
98        Event => $Param{Event},
99    );
100
101    # return if no notification for event exists
102    return 1 if !@IDs;
103
104    # get ticket attribute matches
105    my %Ticket = $TicketObject->TicketGet(
106        TicketID      => $Param{Data}->{TicketID},
107        UserID        => $Param{UserID},
108        DynamicFields => 1,
109    );
110
111    # get dynamic field objects
112    my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
113
114    # get dynamic fields
115    my $DynamicFieldList = $DynamicFieldObject->DynamicFieldListGet(
116        Valid      => 1,
117        ObjectType => ['Ticket'],
118    );
119
120    # create a dynamic field config lookup table
121    my %DynamicFieldConfigLookup;
122    for my $DynamicFieldConfig ( @{$DynamicFieldList} ) {
123        $DynamicFieldConfigLookup{ $DynamicFieldConfig->{Name} } = $DynamicFieldConfig;
124    }
125
126    my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
127
128    NOTIFICATION:
129    for my $ID (@IDs) {
130
131        my %Notification = $NotificationEventObject->NotificationGet(
132            ID => $ID,
133        );
134
135        # verify ticket and article conditions
136        my $PassFilter = $Self->_NotificationFilter(
137            %Param,
138            Ticket                   => \%Ticket,
139            Notification             => \%Notification,
140            DynamicFieldConfigLookup => \%DynamicFieldConfigLookup,
141        );
142        next NOTIFICATION if !$PassFilter;
143
144        # add attachments only on ArticleCreate or ArticleSend event
145        my @Attachments;
146        if (
147            ( ( $Param{Event} eq 'ArticleCreate' ) || ( $Param{Event} eq 'ArticleSend' ) )
148            && $Param{Data}->{ArticleID}
149            )
150        {
151
152            # add attachments to notification
153            if ( $Notification{Data}->{ArticleAttachmentInclude}->[0] ) {
154
155                my $BackendObject = $ArticleObject->BackendForArticle(
156                    TicketID  => $Param{Data}->{TicketID},
157                    ArticleID => $Param{Data}->{ArticleID},
158                );
159
160                my %Index = $BackendObject->ArticleAttachmentIndex(
161                    ArticleID        => $Param{Data}->{ArticleID},
162                    ExcludePlainText => 1,
163                    ExcludeHTMLBody  => 1,
164                );
165                if (%Index) {
166                    FILE_ID:
167                    for my $FileID ( sort keys %Index ) {
168                        my %Attachment = $BackendObject->ArticleAttachment(
169                            ArticleID => $Param{Data}->{ArticleID},
170                            FileID    => $FileID,
171                        );
172                        next FILE_ID if !%Attachment;
173                        push @Attachments, \%Attachment;
174                    }
175                }
176            }
177        }
178
179        # get recipients
180        my @RecipientUsers = $Self->_RecipientsGet(
181            %Param,
182            Ticket       => \%Ticket,
183            Notification => \%Notification,
184        );
185
186        my @NotificationBundle;
187
188        # get template generator object
189        my $TemplateGeneratorObject = $Kernel::OM->Get('Kernel::System::TemplateGenerator');
190
191        # parse all notification tags for each user
192        for my $Recipient (@RecipientUsers) {
193
194            my %ReplacedNotification = $TemplateGeneratorObject->NotificationEvent(
195                TicketData            => \%Ticket,
196                Recipient             => $Recipient,
197                Notification          => \%Notification,
198                CustomerMessageParams => $Param{Data}->{CustomerMessageParams},
199                UserID                => $Param{UserID},
200            );
201
202            my $UserNotificationTransport = $Kernel::OM->Get('Kernel::System::JSON')->Decode(
203                Data => $Recipient->{NotificationTransport},
204            );
205
206            push @NotificationBundle, {
207                Recipient                      => $Recipient,
208                Notification                   => \%ReplacedNotification,
209                RecipientNotificationTransport => $UserNotificationTransport,
210            };
211        }
212
213        # get config object
214        my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
215
216        # get notification transport config
217        my %TransportConfig = %{ $ConfigObject->Get('Notification::Transport') || {} };
218
219        # remember already sent agent notifications
220        my %AlreadySent;
221
222        # loop over transports for each notification
223        TRANSPORT:
224        for my $Transport ( sort keys %TransportConfig ) {
225
226            # only configured transports for this notification
227            if ( !grep { $_ eq $Transport } @{ $Notification{Data}->{Transports} } ) {
228                next TRANSPORT;
229            }
230
231            next TRANSPORT if !IsHashRefWithData( $TransportConfig{$Transport} );
232            next TRANSPORT if !$TransportConfig{$Transport}->{Module};
233
234            # get transport object
235            my $TransportObject;
236            eval {
237                $TransportObject = $Kernel::OM->Get( $TransportConfig{$Transport}->{Module} );
238            };
239
240            if ( !$TransportObject ) {
241                $Kernel::OM->Get('Kernel::System::Log')->Log(
242                    Priority => 'error',
243                    Message  => "Could not create a new $TransportConfig{$Transport}->{Module} object!",
244                );
245
246                next TRANSPORT;
247            }
248
249            if ( ref $TransportObject ne $TransportConfig{$Transport}->{Module} ) {
250                $Kernel::OM->Get('Kernel::System::Log')->Log(
251                    Priority => 'error',
252                    Message  => "$TransportConfig{$Transport}->{Module} object is invalid",
253                );
254
255                next TRANSPORT;
256            }
257
258            # check if transport is usable
259            next TRANSPORT if !$TransportObject->IsUsable();
260
261            BUNDLE:
262            for my $Bundle (@NotificationBundle) {
263
264                my $UserPreference = "Notification-$Notification{ID}-$Transport";
265
266                # check if agent should get the notification
267                my $AgentSendNotification = 0;
268                if ( defined $Bundle->{RecipientNotificationTransport}->{$UserPreference} ) {
269                    $AgentSendNotification = $Bundle->{RecipientNotificationTransport}->{$UserPreference};
270                }
271                elsif ( grep { $_ eq $Transport } @{ $Notification{Data}->{AgentEnabledByDefault} } ) {
272                    $AgentSendNotification = 1;
273                }
274                elsif (
275                    !IsArrayRefWithData( $Notification{Data}->{VisibleForAgent} )
276                    || (
277                        defined $Notification{Data}->{VisibleForAgent}->[0]
278                        && !$Notification{Data}->{VisibleForAgent}->[0]
279                    )
280                    )
281                {
282                    $AgentSendNotification = 1;
283                }
284
285                # skip sending the notification if the agent has disable it in its preferences
286                if (
287                    IsArrayRefWithData( $Notification{Data}->{VisibleForAgent} )
288                    && $Notification{Data}->{VisibleForAgent}->[0]
289                    && $Bundle->{Recipient}->{Type} eq 'Agent'
290                    && !$AgentSendNotification
291                    )
292                {
293                    next BUNDLE;
294                }
295
296                # Check if notification should not send to the customer.
297                if (
298                    $Bundle->{Recipient}->{Type} eq 'Customer'
299                    && $ConfigObject->Get('CustomerNotifyJustToRealCustomer')
300                    )
301                {
302
303                    # No UserID means it's not a mapped customer.
304                    next BUNDLE if !$Bundle->{Recipient}->{UserID};
305                }
306
307                my $Success = $Self->_SendRecipientNotification(
308                    TicketID              => $Param{Data}->{TicketID},
309                    Notification          => $Bundle->{Notification},
310                    CustomerMessageParams => $Param{Data}->{CustomerMessageParams} || {},
311                    Recipient             => $Bundle->{Recipient},
312                    Event                 => $Param{Event},
313                    Attachments           => \@Attachments,
314                    Transport             => $Transport,
315                    TransportObject       => $TransportObject,
316                    UserID                => $Param{UserID},
317                );
318
319                # remember to have sent
320                if ( $Bundle->{Recipient}->{UserID} ) {
321                    $AlreadySent{ $Bundle->{Recipient}->{UserID} } = 1;
322                }
323            }
324
325            # get special recipients specific for each transport
326            my @TransportRecipients = $TransportObject->GetTransportRecipients(
327                Notification => \%Notification,
328                Ticket       => \%Ticket,
329            );
330
331            next TRANSPORT if !@TransportRecipients;
332
333            RECIPIENT:
334            for my $Recipient (@TransportRecipients) {
335
336                # replace all notification tags for each special recipient
337                my %ReplacedNotification = $TemplateGeneratorObject->NotificationEvent(
338                    TicketData            => \%Ticket,
339                    Recipient             => $Recipient,
340                    Notification          => \%Notification,
341                    CustomerMessageParams => $Param{Data}->{CustomerMessageParams} || {},
342                    UserID                => $Param{UserID},
343                );
344
345                my $Success = $Self->_SendRecipientNotification(
346                    TicketID              => $Param{Data}->{TicketID},
347                    Notification          => \%ReplacedNotification,
348                    CustomerMessageParams => $Param{Data}->{CustomerMessageParams} || {},
349                    Recipient             => $Recipient,
350                    Event                 => $Param{Event},
351                    Attachments           => \@Attachments,
352                    Transport             => $Transport,
353                    TransportObject       => $TransportObject,
354                    UserID                => $Param{UserID},
355                );
356            }
357        }
358    }
359
360    return 1;
361}
362
363sub _NotificationFilter {
364    my ( $Self, %Param ) = @_;
365
366    # check needed params
367    for my $Needed (qw(Ticket Notification DynamicFieldConfigLookup)) {
368        return if !$Param{$Needed};
369    }
370
371    # set local values
372    my %Notification = %{ $Param{Notification} };
373
374    # get dynamic field backend object
375    my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
376
377    my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
378
379    # get the search article fields to retrieve values for
380    my %ArticleSearchableFields = $ArticleObject->ArticleSearchableFieldsList();
381
382    KEY:
383    for my $Key ( sort keys %{ $Notification{Data} } ) {
384
385        # TODO: This function here should be fixed to not use hardcoded attribute values!
386        # ignore not ticket related attributes
387        next KEY if $Key eq 'Recipients';
388        next KEY if $Key eq 'SkipRecipients';
389        next KEY if $Key eq 'RecipientAgents';
390        next KEY if $Key eq 'RecipientGroups';
391        next KEY if $Key eq 'RecipientRoles';
392        next KEY if $Key eq 'TransportEmailTemplate';
393        next KEY if $Key eq 'Events';
394        next KEY if $Key eq 'ArticleSenderTypeID';
395        next KEY if $Key eq 'ArticleIsVisibleForCustomer';
396        next KEY if $Key eq 'ArticleCommunicationChannelID';
397        next KEY if $Key eq 'ArticleAttachmentInclude';
398        next KEY if $Key eq 'IsVisibleForCustomer';
399        next KEY if $Key eq 'Transports';
400        next KEY if $Key eq 'OncePerDay';
401        next KEY if $Key eq 'VisibleForAgent';
402        next KEY if $Key eq 'VisibleForAgentTooltip';
403        next KEY if $Key eq 'LanguageID';
404        next KEY if $Key eq 'SendOnOutOfOffice';
405        next KEY if $Key eq 'AgentEnabledByDefault';
406        next KEY if $Key eq 'EmailSecuritySettings';
407        next KEY if $Key eq 'EmailSigningCrypting';
408        next KEY if $Key eq 'EmailMissingCryptingKeys';
409        next KEY if $Key eq 'EmailMissingSigningKeys';
410        next KEY if $Key eq 'EmailDefaultSigningKeys';
411        next KEY if $Key eq 'NotificationType';
412
413        # ignore article searchable fields
414        next KEY if $ArticleSearchableFields{$Key};
415
416        # skip transport related attributes
417        if ( $Key =~ m{ \A ( Recipient | Transport ) }xms ) {
418            next KEY;
419        }
420
421        # check ticket attributes
422        next KEY if !defined $Notification{Data}->{$Key};
423        next KEY if !defined $Notification{Data}->{$Key}->[0];
424        next KEY if !@{ $Notification{Data}->{$Key} };
425        my $Match = 0;
426
427        VALUE:
428        for my $Value ( @{ $Notification{Data}->{$Key} } ) {
429
430            next VALUE if !defined $Value;
431
432            # check if key is a search dynamic field
433            if ( $Key =~ m{\A Search_DynamicField_}xms ) {
434
435                # remove search prefix
436                my $DynamicFieldName = $Key;
437
438                $DynamicFieldName =~ s{Search_DynamicField_}{};
439
440                # get the dynamic field config for this field
441                my $DynamicFieldConfig = $Param{DynamicFieldConfigLookup}->{$DynamicFieldName};
442
443                next VALUE if !$DynamicFieldConfig;
444
445                my $IsNotificationEventCondition = $DynamicFieldBackendObject->HasBehavior(
446                    DynamicFieldConfig => $DynamicFieldConfig,
447                    Behavior           => 'IsNotificationEventCondition',
448                );
449
450                next VALUE if !$IsNotificationEventCondition;
451
452                # Get match value from the dynamic field backend, if applicable (bug#12257).
453                my $MatchValue;
454                my $SearchFieldParameter = $DynamicFieldBackendObject->SearchFieldParameterBuild(
455                    DynamicFieldConfig => $DynamicFieldConfig,
456                    Profile            => {
457                        $Key => $Value,
458                    },
459                );
460                if ( defined $SearchFieldParameter->{Parameter}->{Equals} ) {
461                    $MatchValue = $SearchFieldParameter->{Parameter}->{Equals};
462                }
463                else {
464                    $MatchValue = $Value;
465                }
466
467                $Match = $DynamicFieldBackendObject->ObjectMatch(
468                    DynamicFieldConfig => $DynamicFieldConfig,
469                    Value              => $MatchValue,
470                    ObjectAttributes   => $Param{Ticket},
471                );
472
473                last VALUE if $Match;
474            }
475            else {
476
477                if (
478                    $Param{Ticket}->{$Key}
479                    && $Value eq $Param{Ticket}->{$Key}
480                    )
481                {
482                    $Match = 1;
483                    last VALUE;
484                }
485            }
486        }
487
488        return if !$Match;
489    }
490
491    # match article types only on ArticleCreate or ArticleSend event
492    if (
493        ( ( $Param{Event} eq 'ArticleCreate' ) || ( $Param{Event} eq 'ArticleSend' ) )
494        && $Param{Data}->{ArticleID}
495        )
496    {
497        my $BackendObject = $ArticleObject->BackendForArticle(
498            TicketID  => $Param{Data}->{TicketID},
499            ArticleID => $Param{Data}->{ArticleID},
500        );
501
502        my %Article = $BackendObject->ArticleGet(
503            TicketID      => $Param{Data}->{TicketID},
504            ArticleID     => $Param{Data}->{ArticleID},
505            DynamicFields => 0,
506        );
507
508        # Check for active article filters:
509        #   - SenderTypeID
510        #   - IsVisibleForCustomer
511        #   - CommunicationChannelID
512        ARTICLE_FILTER:
513        for my $ArticleFilter (qw(ArticleSenderTypeID ArticleIsVisibleForCustomer ArticleCommunicationChannelID)) {
514            next ARTICLE_FILTER if !$Notification{Data}->{$ArticleFilter};
515
516            my $Match = 0;
517            VALUE:
518            for my $Value ( @{ $Notification{Data}->{$ArticleFilter} } ) {
519                next VALUE if !defined $Value;
520
521                my $ArticleField = $ArticleFilter;
522                $ArticleField =~ s/^Article//;
523
524                if ( $Value == $Article{$ArticleField} ) {
525                    $Match = 1;
526                    last VALUE;
527                }
528            }
529
530            return if !$Match;
531        }
532
533        my %ArticleData = $BackendObject->ArticleSearchableContentGet(
534            TicketID  => $Param{Data}->{TicketID},
535            ArticleID => $Param{Data}->{ArticleID},
536            UserID    => $Param{UserID},
537        );
538
539        # check article backend fields
540        KEY:
541        for my $Key ( sort keys %ArticleSearchableFields ) {
542
543            next KEY if !$Notification{Data}->{$Key};
544
545            my $Match = 0;
546            VALUE:
547            for my $Value ( @{ $Notification{Data}->{$Key} } ) {
548
549                next VALUE if !$Value;
550
551                if ( $ArticleData{$Key}->{String} =~ /\Q$Value\E/i ) {
552                    $Match = 1;
553                    last VALUE;
554                }
555            }
556
557            return if !$Match;
558        }
559    }
560
561    return 1;
562
563}
564
565sub _RecipientsGet {
566    my ( $Self, %Param ) = @_;
567
568    # check needed params
569    for my $Needed (qw(Ticket Notification)) {
570        return if !$Param{$Needed};
571    }
572
573    # set local values
574    my %Notification = %{ $Param{Notification} };
575    my %Ticket       = %{ $Param{Ticket} };
576
577    # get needed objects
578    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
579    my $GroupObject  = $Kernel::OM->Get('Kernel::System::Group');
580    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
581
582    my @RecipientUserIDs;
583    my @RecipientUsers;
584    my @RecipientUserEmails;
585
586    # add pre-calculated recipient
587    if ( IsArrayRefWithData( $Param{Data}->{Recipients} ) ) {
588        push @RecipientUserIDs, @{ $Param{Data}->{Recipients} };
589    }
590
591    # remember pre-calculated user recipients for later comparisons
592    my %PrecalculatedUserIDs = map { $_ => 1 } @RecipientUserIDs;
593
594    my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
595
596    # get recipients by Recipients
597    if ( $Notification{Data}->{Recipients} ) {
598
599        # get needed objects
600        my $QueueObject         = $Kernel::OM->Get('Kernel::System::Queue');
601        my $CustomerUserObject  = $Kernel::OM->Get('Kernel::System::CustomerUser');
602        my $CheckItemObject     = $Kernel::OM->Get('Kernel::System::CheckItem');
603        my $SystemAddressObject = $Kernel::OM->Get('Kernel::System::SystemAddress');
604        my $UserObject          = $Kernel::OM->Get('Kernel::System::User');
605
606        RECIPIENT:
607        for my $Recipient ( @{ $Notification{Data}->{Recipients} } ) {
608
609            if (
610                $Recipient
611                =~ /^Agent(Owner|Responsible|Watcher|WritePermissions|MyQueues|MyServices|MyQueuesMyServices|CreateBy)$/
612                )
613            {
614                if ( $Recipient eq 'AgentOwner' ) {
615                    push @{ $Notification{Data}->{RecipientAgents} }, $Ticket{OwnerID};
616                }
617                elsif ( $Recipient eq 'AgentResponsible' ) {
618
619                    # add the responsible agent to the notification list
620                    if ( $ConfigObject->Get('Ticket::Responsible') && $Ticket{ResponsibleID} ) {
621
622                        push @{ $Notification{Data}->{RecipientAgents} },
623                            $Ticket{ResponsibleID};
624                    }
625                }
626                elsif ( $Recipient eq 'AgentWatcher' ) {
627
628                    # is not needed to check Ticket::Watcher,
629                    # its checked on TicketWatchGet function
630                    push @{ $Notification{Data}->{RecipientAgents} }, $TicketObject->TicketWatchGet(
631                        TicketID => $Param{Data}->{TicketID},
632                        Result   => 'ARRAY',
633                    );
634                }
635                elsif ( $Recipient eq 'AgentWritePermissions' ) {
636
637                    my $GroupID = $QueueObject->GetQueueGroupID(
638                        QueueID => $Ticket{QueueID},
639                    );
640
641                    my %UserList = $GroupObject->PermissionGroupUserGet(
642                        GroupID => $GroupID,
643                        Type    => 'rw',
644                        UserID  => $Param{UserID},
645                    );
646
647                    my %RoleList = $GroupObject->PermissionGroupRoleGet(
648                        GroupID => $GroupID,
649                        Type    => 'rw',
650                    );
651                    for my $RoleID ( sort keys %RoleList ) {
652                        my %RoleUserList = $GroupObject->PermissionRoleUserGet(
653                            RoleID => $RoleID,
654                        );
655                        %UserList = ( %RoleUserList, %UserList );
656                    }
657
658                    my @UserIDs = sort keys %UserList;
659
660                    push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
661                }
662                elsif ( $Recipient eq 'AgentMyQueues' ) {
663
664                    # get subscribed users
665                    my %MyQueuesUserIDs = map { $_ => 1 } $TicketObject->GetSubscribedUserIDsByQueueID(
666                        QueueID => $Ticket{QueueID}
667                    );
668
669                    my @UserIDs = sort keys %MyQueuesUserIDs;
670
671                    push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
672                }
673                elsif ( $Recipient eq 'AgentMyServices' ) {
674
675                    # get subscribed users
676                    my %MyServicesUserIDs;
677                    if ( $Ticket{ServiceID} ) {
678                        %MyServicesUserIDs = map { $_ => 1 } $TicketObject->GetSubscribedUserIDsByServiceID(
679                            ServiceID => $Ticket{ServiceID},
680                        );
681                    }
682
683                    my @UserIDs = sort keys %MyServicesUserIDs;
684
685                    push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
686                }
687                elsif ( $Recipient eq 'AgentMyQueuesMyServices' ) {
688
689                    # get subscribed users
690                    my %MyQueuesUserIDs = map { $_ => 1 } $TicketObject->GetSubscribedUserIDsByQueueID(
691                        QueueID => $Ticket{QueueID}
692                    );
693
694                    # get subscribed users
695                    my %MyServicesUserIDs;
696                    if ( $Ticket{ServiceID} ) {
697                        %MyServicesUserIDs = map { $_ => 1 } $TicketObject->GetSubscribedUserIDsByServiceID(
698                            ServiceID => $Ticket{ServiceID},
699                        );
700                    }
701
702                    # combine both subscribed users list (this will also remove duplicates)
703                    my %SubscribedUserIDs = ( %MyQueuesUserIDs, %MyServicesUserIDs );
704
705                    for my $UserID ( sort keys %SubscribedUserIDs ) {
706                        if ( !$MyQueuesUserIDs{$UserID} || !$MyServicesUserIDs{$UserID} ) {
707                            delete $SubscribedUserIDs{$UserID};
708                        }
709                    }
710
711                    my @UserIDs = sort keys %SubscribedUserIDs;
712
713                    push @{ $Notification{Data}->{RecipientAgents} }, @UserIDs;
714                }
715                elsif ( $Recipient eq 'AgentCreateBy' ) {
716
717                    # Check if the first article was created by an agent.
718                    my @Articles = $ArticleObject->ArticleList(
719                        TicketID   => $Param{Data}->{TicketID},
720                        SenderType => 'agent',
721                        OnlyFirst  => 1,
722                    );
723
724                    if ( $Articles[0] && $Articles[0]->{ArticleNumber} == 1 ) {
725                        push @{ $Notification{Data}->{RecipientAgents} }, $Ticket{CreateBy};
726                    }
727
728                }
729            }
730
731            # Other OTRS packages might add other kind of recipients that are normally handled by
732            #   other modules then an elsif condition here is useful.
733            elsif ( $Recipient eq 'Customer' ) {
734
735                # Get last article from customer.
736                my @CustomerArticles = $ArticleObject->ArticleList(
737                    TicketID   => $Param{Data}->{TicketID},
738                    SenderType => 'customer',
739                    OnlyLast   => 1,
740                );
741
742                my %CustomerArticle;
743
744                ARTICLE:
745                for my $Article (@CustomerArticles) {
746                    next ARTICLE if !$Article->{ArticleID};
747
748                    %CustomerArticle = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet(
749                        %{$Article},
750                        DynamicFields => 0,
751                    );
752                }
753
754                my %Article = %CustomerArticle;
755
756                # If the ticket has no customer article, get the last agent article.
757                if ( !%CustomerArticle ) {
758
759                    # Get last article from agent.
760                    my @AgentArticles = $ArticleObject->ArticleList(
761                        TicketID   => $Param{Data}->{TicketID},
762                        SenderType => 'agent',
763                        OnlyLast   => 1,
764                    );
765
766                    my %AgentArticle;
767
768                    ARTICLE:
769                    for my $Article (@AgentArticles) {
770                        next ARTICLE if !$Article->{ArticleID};
771
772                        %AgentArticle = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet(
773                            %{$Article},
774                            DynamicFields => 0,
775                        );
776                    }
777
778                    %Article = %AgentArticle;
779                }
780
781                # Get raw ticket data.
782                my %Ticket = $TicketObject->TicketGet(
783                    TicketID      => $Param{Data}->{TicketID},
784                    DynamicFields => 0,
785                );
786
787                my %Recipient;
788
789                # When there is no customer article, last agent article will be used. In this case notification must not
790                #   be sent to the "From", but to the "To" article field.
791
792                # Check if we actually do have an article.
793                if ( defined $Article{SenderType} ) {
794                    if ( $Article{SenderType} eq 'customer' ) {
795                        $Recipient{UserEmail} = $Article{From};
796                    }
797                    else {
798                        $Recipient{UserEmail} = $Article{To};
799                    }
800                }
801                $Recipient{Type} = 'Customer';
802
803                # check if customer notifications should be send
804                if (
805                    $ConfigObject->Get('CustomerNotifyJustToRealCustomer')
806                    && !$Ticket{CustomerUserID}
807                    )
808                {
809                    $Kernel::OM->Get('Kernel::System::Log')->Log(
810                        Priority => 'info',
811                        Message  => 'Send no customer notification because no customer is set!',
812                    );
813                    next RECIPIENT;
814                }
815
816                # get language and send recipient
817                $Recipient{Language} = $ConfigObject->Get('DefaultLanguage') || 'en';
818
819                if ( $Ticket{CustomerUserID} ) {
820
821                    my %CustomerUser = $CustomerUserObject->CustomerUserDataGet(
822                        User => $Ticket{CustomerUserID},
823
824                    );
825
826                    # Check if customer user is email address, in case it is not stored in system
827                    if (
828                        !IsHashRefWithData( \%CustomerUser )
829                        && !$ConfigObject->Get('CustomerNotifyJustToRealCustomer')
830                        && $Kernel::OM->Get('Kernel::System::CheckItem')
831                        ->CheckEmail( Address => $Ticket{CustomerUserID} )
832                        )
833                    {
834                        $Recipient{UserEmail} = $Ticket{CustomerUserID};
835                    }
836                    else {
837
838                        # join Recipient data with CustomerUser data
839                        %Recipient = ( %Recipient, %CustomerUser );
840                    }
841
842                    # get user language
843                    if ( $CustomerUser{UserLanguage} ) {
844                        $Recipient{Language} = $CustomerUser{UserLanguage};
845                    }
846                }
847
848                # get real name
849                if ( $Ticket{CustomerUserID} ) {
850                    $Recipient{Realname} = $CustomerUserObject->CustomerName(
851                        UserLogin => $Ticket{CustomerUserID},
852                    );
853                }
854                if ( !$Recipient{Realname} ) {
855                    $Recipient{Realname} = $Article{From} || '';
856                    $Recipient{Realname} =~ s/<.*>|\(.*\)|\"|;|,//g;
857                    $Recipient{Realname} =~ s/( $)|(  $)//g;
858                }
859
860                # Skip notification if email address is already used by other groups.
861                next RECIPIENT if grep { $_ eq $Recipient{UserEmail} } @RecipientUserEmails;
862
863                # Push Email Addresses into array to prevent multiple notifications.
864                push @RecipientUserEmails, $Recipient{UserEmail};
865
866                push @RecipientUsers, \%Recipient;
867            }
868            elsif ( $Recipient eq 'AllRecipientsFirstArticle' || $Recipient eq 'AllRecipientsLastArticle' ) {
869
870                my $SystemSenderType = $ArticleObject->ArticleSenderTypeLookup( SenderType => 'system' );
871
872                my %Article;
873                my @MetaArticles = grep { $_->{SenderTypeID} ne $SystemSenderType } $ArticleObject->ArticleList(
874                    TicketID => $Param{Data}->{TicketID},
875                );
876
877                # Get the first or the last article.
878                if ( $Recipient eq 'AllRecipientsFirstArticle' ) {
879                    @MetaArticles = splice @MetaArticles, 0, 1;
880                }
881                elsif ( $Recipient eq 'AllRecipientsLastArticle' ) {
882                    @MetaArticles = splice @MetaArticles, -1, 1;
883                }
884
885                if (@MetaArticles) {
886                    my $ArticleBackend = $ArticleObject->BackendForArticle( %{ $MetaArticles[0] } );
887                    if ( $ArticleBackend->ChannelNameGet() ne 'Email' ) {
888                        next RECIPIENT;
889
890                    }
891                    %Article = $ArticleBackend->ArticleGet(
892                        %{ $MetaArticles[0] },
893                        DynamicFields => 0,
894                    );
895                }
896
897                if ( !%Article ) {
898                    next RECIPIENT;
899                }
900
901                my %Recipient;
902                my @AllRecipients;
903                my @TmpRecipients;
904                my @TmpRecipientAgents;
905                my @RecipientAgents;
906
907                # Get recipient agents to prevent multiple notifications
908                if ( IsArrayRefWithData( $Notification{Data}->{RecipientAgents} ) ) {
909                    @RecipientAgents = @{ $Notification{Data}->{RecipientAgents} };
910                }
911
912                if (@RecipientAgents) {
913                    for my $UserID (@RecipientAgents) {
914
915                        my %User = $UserObject->GetUserData(
916                            UserID => $UserID,
917                        );
918
919                        push @TmpRecipientAgents, $User{UserEmail};
920                    }
921                }
922
923                # Get all recipients from the article.
924                ALLRECIPIENTS:
925                for my $Header (qw(From To Cc)) {
926
927                    next ALLRECIPIENTS if !$Article{$Header};
928
929                    push @TmpRecipients, split ',', $Article{$Header};
930                }
931
932                # Loop through recipients.
933                EMAIL:
934                for my $Email ( Mail::Address->parse(@TmpRecipients) ) {
935
936                    # Skip notification if email address is already used by other groups.
937                    next EMAIL if grep { $_ eq $Email->address() } @RecipientUserEmails;
938
939                    # Validate email address.
940                    my $Valid = $CheckItemObject->CheckEmail(
941                        Address => $Email->address(),
942                    );
943
944                    # Skip invalid.
945                    next EMAIL if !$Valid;
946
947                    # Check if email address is a local.
948                    my $IsLocal = $SystemAddressObject->SystemAddressIsLocalAddress(
949                        Address => $Email->address(),
950                    );
951
952                    # Skip local email address.
953                    next EMAIL if $IsLocal;
954
955                    # Skip email addresses from agents selected by other groups.
956                    next EMAIL if grep { $_ eq $Email->address() } @TmpRecipientAgents;
957
958                    push @AllRecipients, $Email->address();
959
960                    # Push Email Addresses into array to prevent multiple notifications.
961                    push @RecipientUserEmails, $Email->address();
962                }
963
964                # Merge recipients.
965                $Recipient{UserEmail} = join( ',', @AllRecipients );
966
967                $Recipient{Type} = 'Customer';
968
969                # Get user language.
970                $Recipient{Language} = $ConfigObject->Get('DefaultLanguage') || 'en';
971
972                push @RecipientUsers, \%Recipient;
973            }
974        }
975    }
976
977    # add recipient agents
978    if ( IsArrayRefWithData( $Notification{Data}->{RecipientAgents} ) ) {
979        push @RecipientUserIDs, @{ $Notification{Data}->{RecipientAgents} };
980    }
981
982    # hash to keep track which agents are already receiving this notification
983    my %AgentUsed = map { $_ => 1 } @RecipientUserIDs;
984
985    # get recipients by RecipientGroups
986    if ( $Notification{Data}->{RecipientGroups} ) {
987
988        RECIPIENT:
989        for my $GroupID ( @{ $Notification{Data}->{RecipientGroups} } ) {
990
991            my %GroupMemberList = $GroupObject->PermissionGroupUserGet(
992                GroupID => $GroupID,
993                Type    => 'ro',
994            );
995
996            GROUPMEMBER:
997            for my $UserID ( sort keys %GroupMemberList ) {
998
999                next GROUPMEMBER if $UserID == 1;
1000                next GROUPMEMBER if $AgentUsed{$UserID};
1001
1002                $AgentUsed{$UserID} = 1;
1003
1004                push @RecipientUserIDs, $UserID;
1005            }
1006        }
1007    }
1008
1009    # get recipients by RecipientRoles
1010    if ( $Notification{Data}->{RecipientRoles} ) {
1011
1012        RECIPIENT:
1013        for my $RoleID ( @{ $Notification{Data}->{RecipientRoles} } ) {
1014
1015            my %RoleMemberList = $GroupObject->PermissionRoleUserGet(
1016                RoleID => $RoleID,
1017            );
1018
1019            ROLEMEMBER:
1020            for my $UserID ( sort keys %RoleMemberList ) {
1021
1022                next ROLEMEMBER if $UserID == 1;
1023                next ROLEMEMBER if $AgentUsed{$UserID};
1024
1025                $AgentUsed{$UserID} = 1;
1026
1027                push @RecipientUserIDs, $UserID;
1028            }
1029        }
1030    }
1031
1032    # get needed objects
1033    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
1034
1035    my %SkipRecipients;
1036    if ( IsArrayRefWithData( $Param{Data}->{SkipRecipients} ) ) {
1037        %SkipRecipients = map { $_ => 1 } @{ $Param{Data}->{SkipRecipients} };
1038    }
1039
1040    # agent 1 should not receive notifications
1041    $SkipRecipients{'1'} = 1;
1042
1043    # remove recipients should not receive a notification
1044    @RecipientUserIDs = grep { !$SkipRecipients{$_} } @RecipientUserIDs;
1045
1046    # get valid users list
1047    my %ValidUsersList = $UserObject->UserList(
1048        Type          => 'Short',
1049        Valid         => 1,
1050        NoOutOfOffice => 0,
1051    );
1052
1053    # remove invalid users
1054    @RecipientUserIDs = grep { $ValidUsersList{$_} } @RecipientUserIDs;
1055
1056    # remove duplicated
1057    my %TempRecipientUserIDs = map { $_ => 1 } @RecipientUserIDs;
1058    @RecipientUserIDs = sort keys %TempRecipientUserIDs;
1059
1060    # get time object
1061    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
1062
1063    # get all data for recipients as they should be needed by all notification transports
1064    RECIPIENT:
1065    for my $UserID (@RecipientUserIDs) {
1066
1067        my %User = $UserObject->GetUserData(
1068            UserID => $UserID,
1069            Valid  => 1,
1070        );
1071        next RECIPIENT if !%User;
1072
1073        # skip user that triggers the event (it should not be notified) but only if it is not
1074        #   a pre-calculated recipient
1075        if (
1076            !$ConfigObject->Get('AgentSelfNotifyOnAction')
1077            && $User{UserID} == $Param{UserID}
1078            && !$PrecalculatedUserIDs{ $Param{UserID} }
1079            )
1080        {
1081            next RECIPIENT;
1082        }
1083
1084        # skip users out of the office if configured
1085        if ( !$Notification{Data}->{SendOnOutOfOffice} && $User{OutOfOffice} ) {
1086            my $Start = sprintf(
1087                "%04d-%02d-%02d 00:00:00",
1088                $User{OutOfOfficeStartYear}, $User{OutOfOfficeStartMonth},
1089                $User{OutOfOfficeStartDay}
1090            );
1091            my $TimeStart = $Kernel::OM->Create(
1092                'Kernel::System::DateTime',
1093                ObjectParams => {
1094                    String => $Start,
1095                }
1096            );
1097            my $End = sprintf(
1098                "%04d-%02d-%02d 23:59:59",
1099                $User{OutOfOfficeEndYear}, $User{OutOfOfficeEndMonth},
1100                $User{OutOfOfficeEndDay}
1101            );
1102            my $TimeEnd = $Kernel::OM->Create(
1103                'Kernel::System::DateTime',
1104                ObjectParams => {
1105                    String => $End,
1106                }
1107            );
1108
1109            next RECIPIENT if $TimeStart < $DateTimeObject && $TimeEnd > $DateTimeObject;
1110        }
1111
1112        # skip users with out ro permissions
1113        my $Permission = $TicketObject->TicketPermission(
1114            Type     => 'ro',
1115            TicketID => $Ticket{TicketID},
1116            UserID   => $User{UserID}
1117        );
1118
1119        # Additional permissions for notes.
1120        # Please see bug#14917 for more information.
1121        if ( !$Permission && $Param{Event} eq 'NotificationAddNote' ) {
1122            $Permission = $TicketObject->TicketPermission(
1123                Type     => 'note',
1124                TicketID => $Ticket{TicketID},
1125                UserID   => $User{UserID}
1126            );
1127        }
1128
1129        next RECIPIENT if !$Permission;
1130
1131        # skip PostMasterUserID
1132        my $PostmasterUserID = $ConfigObject->Get('PostmasterUserID') || 1;
1133        next RECIPIENT if $User{UserID} == $PostmasterUserID;
1134
1135        $User{Type} = 'Agent';
1136
1137        push @RecipientUsers, \%User;
1138    }
1139
1140    return @RecipientUsers;
1141}
1142
1143sub _SendRecipientNotification {
1144    my ( $Self, %Param ) = @_;
1145
1146    # check needed stuff
1147    for my $Needed (qw(TicketID UserID Notification Recipient Event Transport TransportObject)) {
1148        if ( !$Param{$Needed} ) {
1149            $Kernel::OM->Get('Kernel::System::Log')->Log(
1150                Priority => 'error',
1151                Message  => "Need $Needed!",
1152            );
1153        }
1154    }
1155
1156    # get ticket object
1157    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
1158
1159    # check if the notification needs to be sent just one time per day
1160    if ( $Param{Notification}->{Data}->{OncePerDay} && $Param{Recipient}->{UserLogin} ) {
1161
1162        # get ticket history
1163        my @HistoryLines = $TicketObject->HistoryGet(
1164            TicketID => $Param{TicketID},
1165            UserID   => $Param{UserID},
1166        );
1167
1168        # get last notification sent ticket history entry for this transport and this user
1169        my $LastNotificationHistory;
1170        if ( defined $Param{Recipient}->{Source} && $Param{Recipient}->{Source} eq 'CustomerUser' ) {
1171            $LastNotificationHistory = first {
1172                $_->{HistoryType} eq 'SendCustomerNotification'
1173                    && $_->{Name} eq
1174                    "\%\%$Param{Recipient}->{UserEmail}"
1175            }
1176            reverse @HistoryLines;
1177        }
1178        else {
1179            $LastNotificationHistory = first {
1180                $_->{HistoryType} eq 'SendAgentNotification'
1181                    && $_->{Name} eq
1182                    "\%\%$Param{Notification}->{Name}\%\%$Param{Recipient}->{UserLogin}\%\%$Param{Transport}"
1183            }
1184            reverse @HistoryLines;
1185        }
1186
1187        if ( $LastNotificationHistory && $LastNotificationHistory->{CreateTime} ) {
1188
1189            my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
1190
1191            my $LastNotificationDateTimeObject = $Kernel::OM->Create(
1192                'Kernel::System::DateTime',
1193                ObjectParams => {
1194                    String => $LastNotificationHistory->{CreateTime},
1195                },
1196            );
1197
1198            # do not send the notification if it has been sent already today
1199            if (
1200                $DateTimeObject->Format( Format => "%Y-%m-%d" ) eq
1201                $LastNotificationDateTimeObject->Format( Format => "%Y-%m-%d" )
1202                )
1203            {
1204                return;
1205            }
1206        }
1207    }
1208
1209    my $TransportObject = $Param{TransportObject};
1210
1211    # send notification to each recipient
1212    my $Success = $TransportObject->SendNotification(
1213        TicketID              => $Param{TicketID},
1214        UserID                => $Param{UserID},
1215        Notification          => $Param{Notification},
1216        CustomerMessageParams => $Param{CustomerMessageParams},
1217        Recipient             => $Param{Recipient},
1218        Event                 => $Param{Event},
1219        Attachments           => $Param{Attachments},
1220    );
1221
1222    return if !$Success;
1223
1224    if (
1225        $Param{Recipient}->{Type} eq 'Agent'
1226        && $Param{Recipient}->{UserLogin}
1227        )
1228    {
1229
1230        # write history
1231        $TicketObject->HistoryAdd(
1232            TicketID     => $Param{TicketID},
1233            HistoryType  => 'SendAgentNotification',
1234            Name         => "\%\%$Param{Notification}->{Name}\%\%$Param{Recipient}->{UserLogin}\%\%$Param{Transport}",
1235            CreateUserID => $Param{UserID},
1236        );
1237    }
1238
1239    my %EventData = %{ $TransportObject->GetTransportEventData() };
1240
1241    return 1 if !%EventData;
1242
1243    if ( !$EventData{Event} || !$EventData{Data} || !$EventData{UserID} ) {
1244
1245        $Kernel::OM->Get('Kernel::System::Log')->Log(
1246            Priority => 'error',
1247            Message  => "Could not trigger notification post send event",
1248        );
1249
1250        return;
1251    }
1252
1253    # ticket event
1254    $TicketObject->EventHandler(
1255        %EventData,
1256    );
1257
1258    return 1;
1259}
1260
12611;
1262