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::CalendarTemplateGenerator; 10 11use strict; 12use warnings; 13 14use Kernel::Language; 15 16use Kernel::System::VariableCheck qw(:all); 17 18our @ObjectDependencies = ( 19 'Kernel::Config', 20 'Kernel::System::Calendar', 21 'Kernel::System::Calendar::Appointment', 22 'Kernel::System::DateTime', 23 'Kernel::System::HTMLUtils', 24 'Kernel::System::Log', 25 'Kernel::System::User', 26 'Kernel::System::Main', 27 'Kernel::System::Group', 28 'Kernel::System::Valid', 29); 30 31=head1 NAME 32 33Kernel::System::CalendarTemplateGenerator - signature lib 34 35=head1 DESCRIPTION 36 37All signature functions. 38 39=head1 PUBLIC INTERFACE 40 41=head2 new() 42 43create an object. Do not use it directly, instead use: 44 45 use Kernel::System::ObjectManager; 46 local $Kernel::OM = Kernel::System::ObjectManager->new(); 47 my $TemplateGeneratorObject = $Kernel::OM->Get('Kernel::System::TemplateGenerator'); 48 49=cut 50 51sub new { 52 my ( $Type, %Param ) = @_; 53 54 # allocate new hash for object 55 my $Self = {}; 56 bless( $Self, $Type ); 57 58 $Self->{RichText} = $Kernel::OM->Get('Kernel::Config')->Get('Frontend::RichText'); 59 60 return $Self; 61} 62 63=head2 NotificationEvent() 64 65replace all OTRS smart tags in the notification body and subject 66 67 my %NotificationEvent = $CalendarTemplateGeneratorObject->NotificationEvent( 68 AppointmentID => 123, 69 Recipient => $UserDataHashRef, # Agent data get result 70 Notification => $NotificationDataHashRef, 71 UserID => 123, 72 ); 73 74=cut 75 76sub NotificationEvent { 77 my ( $Self, %Param ) = @_; 78 79 # check needed stuff 80 for my $Needed (qw(Notification Recipient UserID)) { 81 if ( !$Param{$Needed} ) { 82 $Kernel::OM->Get('Kernel::System::Log')->Log( 83 Priority => 'error', 84 Message => "Need $Needed!", 85 ); 86 return; 87 } 88 } 89 90 if ( !IsHashRefWithData( $Param{Notification} ) ) { 91 $Kernel::OM->Get('Kernel::System::Log')->Log( 92 Priority => 'error', 93 Message => "Notification is invalid!", 94 ); 95 return; 96 } 97 98 my %Notification = %{ $Param{Notification} }; 99 100 # get system default language 101 my $DefaultLanguage = $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage') || 'en'; 102 103 my $Languages = [ $Param{Recipient}->{UserLanguage}, $DefaultLanguage, 'en' ]; 104 105 my $Language; 106 LANGUAGE: 107 for my $Item ( @{$Languages} ) { 108 next LANGUAGE if !$Item; 109 next LANGUAGE if !$Notification{Message}->{$Item}; 110 111 # set language 112 $Language = $Item; 113 last LANGUAGE; 114 } 115 116 # if no language, then take the first one available 117 if ( !$Language ) { 118 my @NotificationLanguages = sort keys %{ $Notification{Message} }; 119 $Language = $NotificationLanguages[0]; 120 } 121 122 # copy the correct language message attributes to a flat structure 123 for my $Attribute (qw(Subject Body ContentType)) { 124 $Notification{$Attribute} = $Notification{Message}->{$Language}->{$Attribute}; 125 } 126 127 my $Start = '<'; 128 my $End = '>'; 129 if ( $Notification{ContentType} =~ m{text\/html} ) { 130 $Start = '<'; 131 $End = '>'; 132 } 133 134 # get html utils object 135 my $HTMLUtilsObject = $Kernel::OM->Get('Kernel::System::HTMLUtils'); 136 137 # do text/plain to text/html convert 138 if ( $Self->{RichText} && $Notification{ContentType} =~ /text\/plain/i ) { 139 $Notification{ContentType} = 'text/html'; 140 $Notification{Body} = $HTMLUtilsObject->ToHTML( 141 String => $Notification{Body}, 142 ); 143 } 144 145 # do text/html to text/plain convert 146 if ( !$Self->{RichText} && $Notification{ContentType} =~ /text\/html/i ) { 147 $Notification{ContentType} = 'text/plain'; 148 $Notification{Body} = $HTMLUtilsObject->ToAscii( 149 String => $Notification{Body}, 150 ); 151 } 152 153 # get notify texts 154 for my $Text (qw(Subject Body)) { 155 if ( !$Notification{$Text} ) { 156 $Notification{$Text} = "No Notification $Text for $Param{Type} found!"; 157 } 158 } 159 160 # replace place holder stuff 161 $Notification{Body} = $Self->_Replace( 162 RichText => $Self->{RichText}, 163 Text => $Notification{Body}, 164 Recipient => $Param{Recipient}, 165 AppointmentID => $Param{AppointmentID}, 166 CalendarID => $Param{CalendarID}, 167 UserID => $Param{UserID}, 168 Language => $Language, 169 ); 170 171 $Notification{Subject} = $Self->_Replace( 172 RichText => 0, 173 Text => $Notification{Subject}, 174 Recipient => $Param{Recipient}, 175 AppointmentID => $Param{AppointmentID}, 176 CalendarID => $Param{CalendarID}, 177 UserID => $Param{UserID}, 178 Language => $Language, 179 ); 180 181 # add URLs and verify to be full HTML document 182 if ( $Self->{RichText} ) { 183 184 $Notification{Body} = $HTMLUtilsObject->LinkQuote( 185 String => $Notification{Body}, 186 ); 187 } 188 189 return %Notification; 190} 191 192=begin Internal: 193 194=cut 195 196sub _Replace { 197 my ( $Self, %Param ) = @_; 198 199 # check needed stuff 200 for (qw(Text RichText UserID)) { 201 if ( !defined $Param{$_} ) { 202 $Kernel::OM->Get('Kernel::System::Log')->Log( 203 Priority => 'error', 204 Message => "Need $_!" 205 ); 206 return; 207 } 208 } 209 210 # check for mailto links 211 # since the subject and body of those mailto links are 212 # uri escaped we have to uri unescape them, replace 213 # possible placeholders and then re-uri escape them 214 $Param{Text} =~ s{ 215 (href="mailto:[^\?]+\?)([^"]+") 216 } 217 { 218 my $MailToHref = $1; 219 my $MailToHrefContent = $2; 220 221 $MailToHrefContent =~ s{ 222 ((?:subject|body)=)(.+?)("|&) 223 } 224 { 225 my $SubjectOrBodyPrefix = $1; 226 my $SubjectOrBodyContent = $2; 227 my $SubjectOrBodySuffix = $3; 228 229 my $SubjectOrBodyContentUnescaped = URI::Escape::uri_unescape $SubjectOrBodyContent; 230 231 my $SubjectOrBodyContentReplaced = $Self->_Replace( 232 %Param, 233 Text => $SubjectOrBodyContentUnescaped, 234 RichText => 0, 235 ); 236 237 my $SubjectOrBodyContentEscaped = URI::Escape::uri_escape_utf8 $SubjectOrBodyContentReplaced; 238 239 $SubjectOrBodyPrefix . $SubjectOrBodyContentEscaped . $SubjectOrBodySuffix; 240 }egx; 241 242 $MailToHref . $MailToHrefContent; 243 }egx; 244 245 my $Start = '<'; 246 my $End = '>'; 247 if ( $Param{RichText} ) { 248 $Start = '<'; 249 $End = '>'; 250 $Param{Text} =~ s/(\n|\r)//g; 251 } 252 253 # get needed objects 254 my $AppointmentObject = $Kernel::OM->Get('Kernel::System::Calendar::Appointment'); 255 my $CalendarObject = $Kernel::OM->Get('Kernel::System::Calendar'); 256 257 my %Calendar; 258 my %Appointment; 259 260 if ( $Param{AppointmentID} ) { 261 %Appointment = $AppointmentObject->AppointmentGet( 262 AppointmentID => $Param{AppointmentID}, 263 ); 264 %Calendar = $CalendarObject->CalendarGet( 265 CalendarID => $Appointment{CalendarID} || $Param{CalendarID}, 266 ); 267 } 268 elsif ( $Param{CalendarID} ) { 269 %Calendar = $CalendarObject->CalendarGet( 270 CalendarID => $Param{CalendarID}, 271 ); 272 } 273 274 # get config object 275 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 276 277 # special replace from secret config options 278 my @SecretConfigOptions = qw( 279 DatabasePw 280 SearchUserPw 281 UserPw 282 SendmailModule::AuthPassword 283 AuthModule::Radius::Password 284 PGP::Key::Password 285 Customer::AuthModule::DB::CustomerPassword 286 Customer::AuthModule::Radius::Password 287 PublicFrontend::AuthPassword 288 ); 289 290 # replace the secret config options before the normal config options 291 for my $SecretConfigOption (@SecretConfigOptions) { 292 293 my $Tag = $Start . 'OTRS_CONFIG_' . $SecretConfigOption . $End; 294 $Param{Text} =~ s{$Tag}{xxx}gx; 295 } 296 297 # replace config options 298 my $Tag = $Start . 'OTRS_CONFIG_'; 299 $Param{Text} =~ s{$Tag(.+?)$End}{$ConfigObject->Get($1) // ''}egx; 300 301 # cleanup 302 $Param{Text} =~ s/$Tag.+?$End/-/gi; 303 304 my %Recipient = %{ $Param{Recipient} || {} }; 305 306 # get user object 307 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 308 309 if ( !%Recipient && $Param{RecipientID} ) { 310 311 %Recipient = $UserObject->GetUserData( 312 UserID => $Param{RecipientID}, 313 NoOutOfOffice => 1, 314 ); 315 } 316 317 # Instantiate a new language object with the given language. 318 my $LanguageObject = Kernel::Language->new( 319 UserLanguage => $Param{Language} || 'en', 320 ); 321 322 # supported appointment fields 323 my %AppointmentTagsSkip = ( 324 ParentID => 1, 325 RecurrenceType => 1, 326 RecurrenceFrequency => 1, 327 RecurrenceCount => 1, 328 RecurrenceInterval => 1, 329 RecurrenceUntil => 1, 330 RecurrenceID => 1, 331 RecurrenceExclude => 1, 332 NotificationCustom => 1, 333 NotificationTemplate => 1, 334 NotificationCustomUnitCount => 1, 335 NotificationCustomUnit => 1, 336 NotificationCustomUnitPointOfTime => 1, 337 NotificationCustomRelativePointOfTime => 1, 338 NotificationCustomRelativeUnit => 1, 339 NotificationCustomRelativeUnitCount => 1, 340 NotificationCustomDateTime => 1, 341 ); 342 343 # ------------------------------------------------------------ # 344 # process appointment 345 # ------------------------------------------------------------ # 346 347 # replace config options 348 $Tag = $Start . 'OTRS_APPOINTMENT_'; 349 350 # replace appointment tags 351 ATTRIBUTE: 352 for my $Attribute ( sort keys %Appointment ) { 353 354 next ATTRIBUTE if !$Attribute; 355 next ATTRIBUTE if $AppointmentTagsSkip{$Attribute}; 356 357 # setup a new tag for the current attribute 358 my $MatchTag = $Tag . uc $Attribute; 359 360 # map NotificationTime attribute 361 if ( $Attribute eq 'NotificationDate' ) { 362 $MatchTag = $Tag . 'NOTIFICATIONTIME'; 363 } 364 365 my $Replacement = ''; 366 367 # process datetime strings (timestamps) 368 if ( 369 $Attribute eq 'StartTime' 370 || $Attribute eq 'EndTime' 371 || $Attribute eq 'NotificationDate' 372 || $Attribute eq 'CreateTime' 373 || $Attribute eq 'ChangeTime' 374 ) 375 { 376 if ( !$Appointment{$Attribute} ) { 377 $Replacement = '-'; 378 } 379 else { 380 381 # Get the stored date time. 382 my $TagSystemTimeObject = $Kernel::OM->Create( 383 'Kernel::System::DateTime', 384 ObjectParams => { 385 String => $Appointment{$Attribute}, 386 }, 387 ); 388 389 # Convert to recipients time zone. 390 $TagSystemTimeObject->ToTimeZone( 391 TimeZone => $Recipient{UserTimeZone} 392 // $TagSystemTimeObject->UserDefaultTimeZoneGet(), 393 ); 394 395 my $DateFormat = 'DateFormat'; 396 397 # Do not include time component for all-day appointments, 398 # but only for start and end dates. 399 if ( 400 $Appointment{AllDay} 401 && ( 402 $Attribute eq 'StartTime' 403 || $Attribute eq 'EndTime' 404 ) 405 ) 406 { 407 $DateFormat .= 'Short'; 408 } 409 410 # Prepare dates and times. 411 $Replacement = $LanguageObject->FormatTimeString( $TagSystemTimeObject->ToString(), $DateFormat ) || ''; 412 } 413 } 414 415 # process createby and changeby 416 elsif ( $Attribute eq 'CreateBy' || $Attribute eq 'ChangeBy' ) { 417 418 $Replacement = $UserObject->UserName( 419 UserID => $Appointment{$Attribute}, 420 ); 421 } 422 423 # process team ids 424 elsif ( $Attribute eq 'TeamID' ) { 425 426 next ATTRIBUTE if !IsArrayRefWithData( $Appointment{$Attribute} ); 427 428 if ( 429 !$Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::Calendar::Team', Silent => 1 ) 430 ) 431 { 432 next ATTRIBUTE; 433 } 434 435 # instanciate a new team object 436 my $TeamObject = Kernel::System::Calendar::Team->new(); 437 438 # get a list of available (readable) teams 439 my %TeamList = $TeamObject->TeamList( 440 Valid => 0, 441 UserID => $Self->{UserID}, 442 ); 443 444 next ATTRIBUTE if !IsHashRefWithData( \%TeamList ); 445 446 my @TeamNames; 447 448 if ( IsHashRefWithData( \%TeamList ) ) { 449 450 TEAMKEY: 451 for my $TeamKey ( @{ $Appointment{$Attribute} } ) { 452 453 next TEAMKEY if !$TeamList{$TeamKey}; 454 455 push @TeamNames, $TeamList{$TeamKey}; 456 } 457 } 458 459 next ATTRIBUTE if !IsArrayRefWithData( \@TeamNames ); 460 461 # replace team ids with a comma seperated list of team names 462 $Replacement = join ', ', @TeamNames; 463 } 464 465 # process resource ids 466 elsif ( $Attribute eq 'ResourceID' ) { 467 468 next ATTRIBUTE if !IsArrayRefWithData( $Appointment{$Attribute} ); 469 470 my @UserNames; 471 472 USERID: 473 for my $UserID ( @{ $Appointment{$Attribute} } ) { 474 475 next USERID if !$UserID; 476 477 my $UserName = $UserObject->UserName( 478 UserID => $UserID, 479 ); 480 481 next USERID if !$UserName; 482 483 push @UserNames, $UserName; 484 } 485 486 next ATTRIBUTE if !IsArrayRefWithData( \@UserNames ); 487 488 # replace resource ids with a comma seperated list of team names 489 $Replacement = join ', ', @UserNames; 490 } 491 492 # process all day and recurring tags 493 elsif ( 494 $Attribute eq 'AllDay' 495 || $Attribute eq 'Recurring' 496 ) 497 { 498 my $TranslatedString = $LanguageObject->Translate('No'); 499 500 if ( $Appointment{$Attribute} ) { 501 $TranslatedString = $LanguageObject->Translate('Yes'); 502 } 503 504 $Replacement = $TranslatedString; 505 } 506 507 # process description tag 508 elsif ( 509 $Attribute eq 'Description' 510 && $Self->{RichText} 511 && $Appointment{$Attribute} 512 ) 513 { 514 my $HTMLUtilsObject = $Kernel::OM->Get('Kernel::System::HTMLUtils'); 515 $Replacement = $HTMLUtilsObject->ToHTML( 516 String => $Appointment{$Attribute}, 517 ); 518 } 519 520 # process all other single values 521 else { 522 if ( !$Appointment{$Attribute} ) { 523 $Replacement = '-'; 524 } 525 else { 526 $Replacement = $Appointment{$Attribute}; 527 } 528 } 529 530 $Replacement ||= ''; 531 532 # replace the tags 533 $Param{Text} =~ s{$MatchTag$End}{$Replacement}egx; 534 } 535 536 # cleanup 537 $Param{Text} =~ s/$Tag.+?$End/-/gi; 538 539 # ------------------------------------------------------------ # 540 # process calendar 541 # ------------------------------------------------------------ # 542 543 # replace config options 544 $Tag = $Start . 'OTRS_CALENDAR_'; 545 546 # replace appointment tags 547 ATTRIBUTE: 548 for my $Attribute ( sort keys %Calendar ) { 549 550 next ATTRIBUTE if !$Attribute; 551 552 # setup a new tag for the current attribute 553 my $MatchTag = $Tag . uc $Attribute; 554 555 my $Replacement = ''; 556 557 # process datetime strings (timestamps) 558 if ( $Attribute eq 'CreateTime' || $Attribute eq 'ChangeTime' ) { 559 560 # Get the stored date time. 561 my $TagSystemTimeObject = $Kernel::OM->Create( 562 'Kernel::System::DateTime', 563 ObjectParams => { 564 String => $Calendar{$Attribute}, 565 }, 566 ); 567 568 # Convert to recipients time zone. 569 $TagSystemTimeObject->ToTimeZone( 570 TimeZone => $Recipient{UserTimeZone} 571 // $TagSystemTimeObject->UserDefaultTimeZoneGet(), 572 ); 573 574 # Prepare dates and times. 575 $Replacement = $LanguageObject->FormatTimeString( $TagSystemTimeObject->ToString(), 'DateFormat' ) || ''; 576 } 577 578 # process createby and changeby 579 elsif ( $Attribute eq 'CreateBy' || $Attribute eq 'ChangeBy' ) { 580 581 $Replacement = $UserObject->UserName( 582 UserID => $Calendar{$Attribute}, 583 ); 584 } 585 586 # process group id 587 elsif ( $Attribute eq 'GroupID' ) { 588 $Replacement = $Kernel::OM->Get('Kernel::System::Group')->GroupLookup( 589 GroupID => $Calendar{$Attribute}, 590 ); 591 } 592 593 # process valid id 594 elsif ( $Attribute eq 'ValidID' ) { 595 596 $Replacement = $Kernel::OM->Get('Kernel::System::Valid')->ValidLookup( 597 ValidID => $Calendar{$Attribute}, 598 ); 599 $Replacement = $LanguageObject->Translate($Replacement); 600 } 601 602 # process all other single values 603 else { 604 if ( !$Calendar{$Attribute} ) { 605 $Replacement = '-'; 606 } 607 else { 608 $Replacement = $Calendar{$Attribute}; 609 } 610 } 611 612 # replace the tags 613 $Param{Text} =~ s{$MatchTag$End}{$Replacement}egx; 614 } 615 616 # cleanup 617 $Param{Text} =~ s/$Tag.+?$End/-/gi; 618 619 my $HashGlobalReplace = sub { 620 my ( $Tag, %H ) = @_; 621 622 # Generate one single matching string for all keys to save performance. 623 my $Keys = join '|', map {quotemeta} grep { defined $H{$_} } keys %H; 624 625 # Add all keys also as lowercase to be able to match case insensitive, 626 # e. g. <OTRS_CUSTOMER_From> and <OTRS_CUSTOMER_FROM>. 627 for my $Key ( sort keys %H ) { 628 $H{ lc $Key } = $H{$Key}; 629 } 630 631 $Param{Text} =~ s/(?:$Tag)($Keys)$End/$H{ lc $1 }/ieg; 632 }; 633 634 # get recipient data and replace it with <OTRS_... 635 $Tag = $Start . 'OTRS_'; 636 637 # include more readable tag <OTRS_NOTIFICATION_RECIPIENT 638 my $RecipientTag = $Start . 'OTRS_NOTIFICATION_RECIPIENT_'; 639 640 if (%Recipient) { 641 642 # HTML quoting of content 643 if ( $Param{RichText} ) { 644 ATTRIBUTE: 645 for my $Attribute ( sort keys %Recipient ) { 646 next ATTRIBUTE if !$Recipient{$Attribute}; 647 $Recipient{$Attribute} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( 648 String => $Recipient{$Attribute}, 649 ); 650 } 651 } 652 653 $HashGlobalReplace->( "$Tag|$RecipientTag", %Recipient ); 654 } 655 656 # cleanup 657 $Param{Text} =~ s/$RecipientTag.+?$End/-/gi; 658 659 return $Param{Text}; 660} 661 6621; 663 664=end Internal: 665 666=head1 TERMS AND CONDITIONS 667 668This software is part of the OTRS project (L<https://otrs.org/>). 669 670This software comes with ABSOLUTELY NO WARRANTY. For details, see 671the enclosed file COPYING for license information (GPL). If you 672did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>. 673 674=cut 675