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