# -- # Copyright (C) 2001-2019 OTRS AG, https://otrs.com/ # -- # This software comes with ABSOLUTELY NO WARRANTY. For details, see # the enclosed file COPYING for license information (GPL). If you # did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. # -- package Kernel::System::TemplateGenerator; ## nofilter(TidyAll::Plugin::OTRS::Perl::LayoutObject) use strict; use warnings; use Kernel::Language; use Kernel::System::VariableCheck qw(:all); our @ObjectDependencies = ( 'Kernel::Config', 'Kernel::System::AutoResponse', 'Kernel::System::CommunicationChannel', 'Kernel::System::CustomerUser', 'Kernel::System::DynamicField', 'Kernel::System::DynamicField::Backend', 'Kernel::System::Encode', 'Kernel::System::HTMLUtils', 'Kernel::System::Log', 'Kernel::System::Queue', 'Kernel::System::Salutation', 'Kernel::System::Signature', 'Kernel::System::StandardTemplate', 'Kernel::System::SystemAddress', 'Kernel::System::Ticket', 'Kernel::System::Ticket::Article', 'Kernel::System::User', 'Kernel::Output::HTML::Layout', 'Kernel::System::DateTime', ); =head1 NAME Kernel::System::TemplateGenerator - signature lib =head1 DESCRIPTION All signature functions. =head1 PUBLIC INTERFACE =head2 new() Don't use the constructor directly, use the ObjectManager instead: my $TemplateGeneratorObject = $Kernel::OM->Get('Kernel::System::TemplateGenerator'); =cut sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {}; bless( $Self, $Type ); $Self->{RichText} = $Kernel::OM->Get('Kernel::Config')->Get('Frontend::RichText'); return $Self; } =head2 Salutation() generate salutation my $Salutation = $TemplateGeneratorObject->Salutation( TicketID => 123, UserID => 123, Data => $ArticleHashRef, ); returns Text ContentType =cut sub Salutation { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID Data UserID)) { if ( !$Param{$_} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $_!" ); return; } } # Get ticket. my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet( TicketID => $Param{TicketID}, DynamicFields => 1, ); # Get queue. my %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( ID => $Ticket{QueueID}, ); # Get salutation. my %Salutation = $Kernel::OM->Get('Kernel::System::Salutation')->SalutationGet( ID => $Queue{SalutationID}, ); # do text/plain to text/html convert if ( $Self->{RichText} && $Salutation{ContentType} =~ /text\/plain/i ) { $Salutation{ContentType} = 'text/html'; $Salutation{Text} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $Salutation{Text}, ); } # do text/html to text/plain convert if ( !$Self->{RichText} && $Salutation{ContentType} =~ /text\/html/i ) { $Salutation{ContentType} = 'text/plain'; $Salutation{Text} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii( String => $Salutation{Text}, ); } # get list unsupported tags for standard template my @ListOfUnSupportedTag = qw(OTRS_AGENT_SUBJECT OTRS_AGENT_BODY OTRS_CUSTOMER_BODY OTRS_CUSTOMER_SUBJECT); my $SalutationText = $Self->_RemoveUnSupportedTag( Text => $Salutation{Text} || '', ListOfUnSupportedTag => \@ListOfUnSupportedTag, ); # replace place holder stuff $SalutationText = $Self->_Replace( RichText => $Self->{RichText}, Text => $SalutationText, TicketData => \%Ticket, Data => $Param{Data}, UserID => $Param{UserID}, ); # add urls if ( $Self->{RichText} ) { $SalutationText = $Kernel::OM->Get('Kernel::System::HTMLUtils')->LinkQuote( String => $SalutationText, ); } return $SalutationText; } =head2 Signature() generate salutation my $Signature = $TemplateGeneratorObject->Signature( TicketID => 123, UserID => 123, Data => $ArticleHashRef, ); or my $Signature = $TemplateGeneratorObject->Signature( QueueID => 123, UserID => 123, Data => $ArticleHashRef, ); returns Text ContentType =cut sub Signature { my ( $Self, %Param ) = @_; # check needed stuff for (qw(Data UserID)) { if ( !$Param{$_} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $_!" ); return; } } # need ticket id or queue id if ( !$Param{TicketID} && !$Param{QueueID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need TicketID or QueueID!' ); return; } # Get ticket data. my %Ticket; if ( $Param{TicketID} ) { %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet( TicketID => $Param{TicketID}, DynamicFields => 1, ); } # Get queue. my %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( ID => $Ticket{QueueID} || $Param{QueueID}, ); # Get signature. my %Signature = $Kernel::OM->Get('Kernel::System::Signature')->SignatureGet( ID => $Queue{SignatureID}, ); # do text/plain to text/html convert if ( $Self->{RichText} && $Signature{ContentType} =~ /text\/plain/i ) { $Signature{ContentType} = 'text/html'; $Signature{Text} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $Signature{Text}, ); } # do text/html to text/plain convert if ( !$Self->{RichText} && $Signature{ContentType} =~ /text\/html/i ) { $Signature{ContentType} = 'text/plain'; $Signature{Text} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii( String => $Signature{Text}, ); } # get list unsupported tags for standard template my @ListOfUnSupportedTag = qw(OTRS_AGENT_SUBJECT OTRS_AGENT_BODY OTRS_CUSTOMER_BODY OTRS_CUSTOMER_SUBJECT); my $SignatureText = $Self->_RemoveUnSupportedTag( Text => $Signature{Text} || '', ListOfUnSupportedTag => \@ListOfUnSupportedTag, ); # replace place holder stuff $SignatureText = $Self->_Replace( RichText => $Self->{RichText}, Text => $SignatureText, TicketData => \%Ticket, Data => $Param{Data}, QueueID => $Param{QueueID}, UserID => $Param{UserID}, ); # add urls if ( $Self->{RichText} ) { $SignatureText = $Kernel::OM->Get('Kernel::System::HTMLUtils')->LinkQuote( String => $SignatureText, ); } return $SignatureText; } =head2 Sender() generate sender address (FROM string) for emails my $Sender = $TemplateGeneratorObject->Sender( QueueID => 123, UserID => 123, ); returns: John Doe at Super Support and it returns the quoted real name if necessary "John Doe, Support" =cut sub Sender { my ( $Self, %Param ) = @_; # check needed stuff for (qw( UserID QueueID)) { if ( !$Param{$_} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get sender attributes my %Address = $Kernel::OM->Get('Kernel::System::Queue')->GetSystemAddress( QueueID => $Param{QueueID}, ); # get config object my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); # check config for agent real name my $UseAgentRealName = $ConfigObject->Get('Ticket::DefineEmailFrom'); if ( $UseAgentRealName && $UseAgentRealName =~ /^(AgentName|AgentNameSystemAddressName)$/ ) { # get data from current agent my %UserData = $Kernel::OM->Get('Kernel::System::User')->GetUserData( UserID => $Param{UserID}, NoOutOfOffice => 1, ); # set real name with user name if ( $UseAgentRealName eq 'AgentName' ) { # check for user data if ( $UserData{UserFullname} ) { # rewrite RealName $Address{RealName} = "$UserData{UserFullname}"; } } # set real name with user name if ( $UseAgentRealName eq 'AgentNameSystemAddressName' ) { # check for user data if ( $UserData{UserFullname} ) { # rewrite RealName my $Separator = ' ' . $ConfigObject->Get('Ticket::DefineEmailFromSeparator') || ''; $Address{RealName} = $UserData{UserFullname} . $Separator . ' ' . $Address{RealName}; } } } # prepare realname quote if ( $Address{RealName} =~ /([.]|,|@|\(|\)|:)/ && $Address{RealName} !~ /^("|')/ ) { $Address{RealName} =~ s/"//g; # remove any quotes that are already present $Address{RealName} = '"' . $Address{RealName} . '"'; } my $Sender = "$Address{RealName} <$Address{Email}>"; return $Sender; } =head2 Template() generate template my $Template = $TemplateGeneratorObject->Template( TemplateID => 123 TicketID => 123, # Optional Data => $ArticleHashRef, # Optional UserID => 123, ); Returns: $Template => 'Some text'; =cut sub Template { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TemplateID UserID)) { if ( !$Param{$_} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $_!" ); return; } } my %Template = $Kernel::OM->Get('Kernel::System::StandardTemplate')->StandardTemplateGet( ID => $Param{TemplateID}, ); # do text/plain to text/html convert if ( $Self->{RichText} && $Template{ContentType} =~ /text\/plain/i && $Template{Template} ) { $Template{ContentType} = 'text/html'; $Template{Template} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $Template{Template}, ); } # do text/html to text/plain convert if ( !$Self->{RichText} && $Template{ContentType} =~ /text\/html/i && $Template{Template} ) { $Template{ContentType} = 'text/plain'; $Template{Template} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii( String => $Template{Template}, ); } # Get user language. my $Language; my %Ticket; if ( defined $Param{TicketID} ) { # Get ticket data. %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet( TicketID => $Param{TicketID}, DynamicFields => 1, ); # Get recipient. my %User = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerUserDataGet( User => $Ticket{CustomerUserID}, ); $Language = $User{UserLanguage}; } # If template type is 'Create' and there is customer user information, treat it as a ticket param in order to # correctly replace customer user tags. See bug#14455. if ( $Template{TemplateType} eq 'Create' && $Param{CustomerUserID} ) { $Ticket{CustomerUserID} = $Param{CustomerUserID}; } # if customer language is not defined, set default language $Language //= $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage') || 'en'; # get list unsupported tags for standard template my @ListOfUnSupportedTag = qw(OTRS_AGENT_SUBJECT OTRS_AGENT_BODY OTRS_CUSTOMER_BODY OTRS_CUSTOMER_SUBJECT); my $TemplateText = $Self->_RemoveUnSupportedTag( Text => $Template{Template} || '', ListOfUnSupportedTag => \@ListOfUnSupportedTag, ); # replace place holder stuff $TemplateText = $Self->_Replace( RichText => $Self->{RichText}, Text => $TemplateText || '', TicketData => \%Ticket, Data => $Param{Data} || {}, UserID => $Param{UserID}, Language => $Language, ); return $TemplateText; } =head2 GenericAgentArticle() generate internal or external notes my $GenericAgentArticle = $TemplateGeneratorObject->GenericAgentArticle( Notification => $NotificationDataHashRef, TicketID => 123, UserID => 123, Data => $ArticleHashRef, # Optional ); =cut sub GenericAgentArticle { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID Notification UserID)) { if ( !$Param{$_} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $_!" ); return; } } my %Template = %{ $Param{Notification} }; # get ticket object my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); # Get ticket data. my %Ticket = $TicketObject->TicketGet( TicketID => $Param{TicketID}, DynamicFields => 1, ); # do text/plain to text/html convert if ( $Self->{RichText} && $Template{ContentType} =~ /text\/plain/i && $Template{Body} ) { $Template{ContentType} = 'text/html'; $Template{Body} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $Template{Body}, ); } # do text/html to text/plain convert if ( !$Self->{RichText} && $Template{ContentType} =~ /text\/html/i && $Template{Body} ) { $Template{ContentType} = 'text/plain'; $Template{Body} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii( String => $Template{Body}, ); } # replace place holder stuff $Template{Body} = $Self->_Replace( RichText => $Self->{RichText}, Text => $Template{Body}, Recipient => $Param{Recipient}, Data => $Param{Data} || {}, TicketData => \%Ticket, UserID => $Param{UserID}, ); $Template{Subject} = $Self->_Replace( RichText => 0, Text => $Template{Subject}, Recipient => $Param{Recipient}, Data => $Param{Data} || {}, TicketData => \%Ticket, UserID => $Param{UserID}, ); $Template{Subject} = $TicketObject->TicketSubjectBuild( TicketNumber => $Ticket{TicketNumber}, Subject => $Template{Subject} || '', Type => 'New', ); # add URLs and verify to be full HTML document if ( $Self->{RichText} ) { $Template{Body} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->LinkQuote( String => $Template{Body}, ); } return %Template; } =head2 Attributes() generate attributes my %Attributes = $TemplateGeneratorObject->Attributes( TicketID => 123, ArticleID => 123, ResponseID => 123 UserID => 123, Action => 'Forward', # Possible values are Reply and Forward, Reply is default. ); returns StandardResponse Salutation Signature =cut sub Attributes { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID Data UserID)) { if ( !$Param{$_} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get ticket object my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); # get queue my %Ticket = $TicketObject->TicketGet( TicketID => $Param{TicketID}, DynamicFields => 0, ); # prepare subject ... $Param{Data}->{Subject} = $TicketObject->TicketSubjectBuild( TicketNumber => $Ticket{TicketNumber}, Subject => $Param{Data}->{Subject} || '', Action => $Param{Action} || '', ); # get sender address $Param{Data}->{From} = $Self->Sender( QueueID => $Ticket{QueueID}, UserID => $Param{UserID}, ); return %{ $Param{Data} }; } =head2 AutoResponse() generate response AutoResponse TicketID Owner Responsible CUSTOMER_DATA ArticleID CUSTOMER_SUBJECT CUSTOMER_EMAIL UserID To Cc Bcc Subject Body ContentType my %AutoResponse = $TemplateGeneratorObject->AutoResponse( TicketID => 123, OrigHeader => {}, AutoResponseType => 'auto reply', UserID => 123, ); =cut sub AutoResponse { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID AutoResponseType OrigHeader UserID)) { if ( !$Param{$_} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get ticket object my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); # Get ticket data. my %Ticket = $TicketObject->TicketGet( TicketID => $Param{TicketID}, DynamicFields => 1, ); # get auto default responses my %AutoResponse = $Kernel::OM->Get('Kernel::System::AutoResponse')->AutoResponseGetByTypeQueueID( QueueID => $Ticket{QueueID}, Type => $Param{AutoResponseType}, ); return if !%AutoResponse; # get old article for quoting my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); my @ArticleList = $ArticleObject->ArticleList( TicketID => $Param{TicketID}, SenderType => 'customer', OnlyLast => 1, ); if ( !@ArticleList ) { @ArticleList = $ArticleObject->ArticleList( TicketID => $Param{TicketID}, IsVisibleForCustomer => 1, OnlyLast => 1, ); } if (@ArticleList) { my %Article = $ArticleObject->BackendForArticle( %{ $ArticleList[0] } )->ArticleGet( %{ $ArticleList[0] } ); for (qw(From To Cc Subject Body)) { if ( !$Param{OrigHeader}->{$_} ) { $Param{OrigHeader}->{$_} = $Article{$_} || ''; } chomp $Param{OrigHeader}->{$_}; } } # format body (only if longer than 86 chars) if ( $Param{OrigHeader}->{Body} ) { if ( length $Param{OrigHeader}->{Body} > 86 ) { my @Lines = split /\n/, $Param{OrigHeader}->{Body}; LINE: for my $Line (@Lines) { my $LineWrapped = $Line =~ s/(^>.+|.{4,86})(?:\s|\z)/$1\n/gm; next LINE if $LineWrapped; # if the regex does not match then we need # to add the missing new line of the split # else we will lose e.g. empty lines of the body. # (bug#10679) $Line .= "\n"; } $Param{OrigHeader}->{Body} = join '', @Lines; } } # fill up required attributes for (qw(Subject Body)) { if ( !$Param{OrigHeader}->{$_} ) { $Param{OrigHeader}->{$_} = "No $_"; } } # get recipient my %User = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerUserDataGet( User => $Ticket{CustomerUserID}, ); # get user language my $Language = $User{UserLanguage} || $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage') || 'en'; # do text/plain to text/html convert if ( $Self->{RichText} && $AutoResponse{ContentType} =~ /text\/plain/i ) { $AutoResponse{ContentType} = 'text/html'; $AutoResponse{Text} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $AutoResponse{Text}, ); } # do text/html to text/plain convert if ( !$Self->{RichText} && $AutoResponse{ContentType} =~ /text\/html/i ) { $AutoResponse{ContentType} = 'text/plain'; $AutoResponse{Text} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii( String => $AutoResponse{Text}, ); } # replace place holder stuff $AutoResponse{Text} = $Self->_Replace( RichText => $Self->{RichText}, Text => $AutoResponse{Text}, Data => { %{ $Param{OrigHeader} }, From => $Param{OrigHeader}->{To}, To => $Param{OrigHeader}->{From}, }, TicketData => \%Ticket, UserID => $Param{UserID}, Language => $Language, AddTimezoneInfo => { AutoResponse => 1, }, ); $AutoResponse{Subject} = $Self->_Replace( RichText => 0, Text => $AutoResponse{Subject}, Data => { %{ $Param{OrigHeader} }, From => $Param{OrigHeader}->{To}, To => $Param{OrigHeader}->{From}, }, TicketData => \%Ticket, UserID => $Param{UserID}, Language => $Language, AddTimezoneInfo => { AutoResponse => 1, }, ); $AutoResponse{Subject} = $TicketObject->TicketSubjectBuild( TicketNumber => $Ticket{TicketNumber}, Subject => $AutoResponse{Subject}, Type => 'New', NoCleanup => 1, ); # get sender attributes based on auto response type if ( $AutoResponse{SystemAddressID} ) { my %Address = $Kernel::OM->Get('Kernel::System::SystemAddress')->SystemAddressGet( ID => $AutoResponse{SystemAddressID}, ); $AutoResponse{SenderAddress} = $Address{Name}; $AutoResponse{SenderRealname} = $Address{Realname}; } # get sender attributes based on queue else { my %Address = $Kernel::OM->Get('Kernel::System::Queue')->GetSystemAddress( QueueID => $Ticket{QueueID}, ); $AutoResponse{SenderAddress} = $Address{Email}; $AutoResponse{SenderRealname} = $Address{RealName}; } # add urls and verify to be full html document if ( $Self->{RichText} ) { $AutoResponse{Text} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->LinkQuote( String => $AutoResponse{Text}, ); $AutoResponse{Text} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->DocumentComplete( Charset => 'utf-8', String => $AutoResponse{Text}, ); } return %AutoResponse; } =head2 NotificationEvent() replace all OTRS smart tags in the notification body and subject my %NotificationEvent = $TemplateGeneratorObject->NotificationEvent( TicketData => $TicketDataHashRef, Recipient => $UserDataHashRef, # Agent or Customer data get result Notification => $NotificationDataHashRef, CustomerMessageParams => $ArticleHashRef, # optional UserID => 123, ); =cut sub NotificationEvent { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(TicketData Notification Recipient UserID)) { if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } if ( !IsHashRefWithData( $Param{Notification} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Notification is invalid!", ); return; } my %Notification = %{ $Param{Notification} }; # exchanging original reference prevent it to grow up if ( ref $Param{CustomerMessageParams} && ref $Param{CustomerMessageParams} eq 'HASH' ) { my %LocalCustomerMessageParams = %{ $Param{CustomerMessageParams} }; $Param{CustomerMessageParams} = \%LocalCustomerMessageParams; } my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article'); # Get last article from customer. my @CustomerArticles = $ArticleObject->ArticleList( TicketID => $Param{TicketData}->{TicketID}, SenderType => 'customer', OnlyLast => 1, ); my %CustomerArticle; ARTICLE: for my $Article (@CustomerArticles) { next ARTICLE if !$Article->{ArticleID}; %CustomerArticle = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet( %{$Article}, DynamicFields => 0, ); } # Get last article from agent. my @AgentArticles = $ArticleObject->ArticleList( TicketID => $Param{TicketData}->{TicketID}, SenderType => 'agent', OnlyLast => 1, ); my %AgentArticle; AGENTARTICLE: for my $Article (@AgentArticles) { next AGENTARTICLE if !$Article->{ArticleID}; %AgentArticle = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet( %{$Article}, DynamicFields => 0, ); # Include the transmission status, if article is an email. my %CommunicationChannel = $Kernel::OM->Get('Kernel::System::CommunicationChannel')->ChannelGet( ChannelID => $Article->{CommunicationChannelID}, ); if ( $CommunicationChannel{ChannelName} eq 'Email' ) { my $TransmissionStatus = $ArticleObject->BackendForArticle( %{$Article} )->ArticleTransmissionStatus( ArticleID => $Article->{ArticleID}, ); if ( $TransmissionStatus && $TransmissionStatus->{Message} ) { $AgentArticle{TransmissionStatusMessage} = $TransmissionStatus->{Message}; } } } my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); ARTICLE: for my $ArticleData ( \%CustomerArticle, \%AgentArticle ) { next ARTICLE if !$ArticleData->{TicketID}; next ARTICLE if !$ArticleData->{ArticleID}; # Get article preview in plain text and store it as Body key. $ArticleData->{Body} = $LayoutObject->ArticlePreview( TicketID => $ArticleData->{TicketID}, ArticleID => $ArticleData->{ArticleID}, ResultType => 'plain', UserID => $Param{UserID}, ); # get accounted time my $AccountedTime = $ArticleObject->ArticleAccountedTimeGet( ArticleID => $ArticleData->{ArticleID}, ); # set the accounted time as part of the articles information $ArticleData->{TimeUnit} = $AccountedTime; } # Populate the hash 'CustomerMessageParams' with all the customer-article data # and overwrite it with 'CustomerMessageParams' passed in the Params (bug #13325). $Param{CustomerMessageParams} = { %CustomerArticle, %{ $Param{CustomerMessageParams} || {} }, }; # get system default language my $DefaultLanguage = $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage') || 'en'; my $Languages = [ $Param{Recipient}->{UserLanguage}, $DefaultLanguage, 'en' ]; my $Language; LANGUAGE: for my $Item ( @{$Languages} ) { next LANGUAGE if !$Item; next LANGUAGE if !$Notification{Message}->{$Item}; # set language $Language = $Item; last LANGUAGE; } # if no language, then take the first one available if ( !$Language ) { my @NotificationLanguages = sort keys %{ $Notification{Message} }; $Language = $NotificationLanguages[0]; } # copy the correct language message attributes to a flat structure for my $Attribute (qw(Subject Body ContentType)) { $Notification{$Attribute} = $Notification{Message}->{$Language}->{$Attribute}; } # Get customer article fields. my %CustomerArticleFields; if (%CustomerArticle) { %CustomerArticleFields = $LayoutObject->ArticleFields( TicketID => $CustomerArticle{TicketID}, ArticleID => $CustomerArticle{ArticleID}, UserID => $Param{UserID}, ); } ARTICLE_FIELD: for my $ArticleField ( sort keys %CustomerArticleFields ) { next ARTICLE_FIELD if !defined $CustomerArticleFields{$ArticleField}->{Value}; if ( !defined $Param{CustomerMessageParams}->{$ArticleField} ) { $Param{CustomerMessageParams}->{$ArticleField} = $CustomerArticleFields{$ArticleField}->{Value}; } chomp $Param{CustomerMessageParams}->{$ArticleField}; } # format body (only if longer the 86 chars) if ( $Param{CustomerMessageParams}->{Body} ) { if ( length $Param{CustomerMessageParams}->{Body} > 86 ) { my @Lines = split /\n/, $Param{CustomerMessageParams}->{Body}; LINE: for my $Line (@Lines) { my $LineWrapped = $Line =~ s/(^>.+|.{4,86})(?:\s|\z)/$1\n/gm; next LINE if $LineWrapped; # if the regex does not match then we need # to add the missing new line of the split # else we will lose e.g. empty lines of the body. # (bug#10679) $Line .= "\n"; } $Param{CustomerMessageParams}->{Body} = join '', @Lines; } } # fill up required attributes for my $Text (qw(Subject Body)) { if ( !$Param{CustomerMessageParams}->{$Text} ) { # Set to last customer article attribute if it is empty string. # For example, if Body is empty string (not undef!), it is maybe sent from NotificationOwnerUpdate event # and overrides last customer article body (in %CustomerArticle) above - see bug#14678. $Param{CustomerMessageParams}->{$Text} = $CustomerArticle{$Text} || "No $Text"; } } my $Start = '<'; my $End = '>'; if ( $Notification{ContentType} =~ m{text\/html} ) { $Start = '<'; $End = '>'; } # replace tags early from CustomerMessageParams, the rests will be replaced # by ticket customer user KEY: for my $Key ( sort keys %{ $Param{CustomerMessageParams} || {} } ) { next KEY if !$Param{CustomerMessageParams}->{$Key}; $Notification{Body} =~ s/${Start}OTRS_CUSTOMER_DATA_$Key${End}/$Param{CustomerMessageParams}->{$Key}/gi; $Notification{Subject} =~ s//$Param{CustomerMessageParams}->{$Key}{$Key}/gi; } # do text/plain to text/html convert if ( $Self->{RichText} && $Notification{ContentType} =~ /text\/plain/i ) { $Notification{ContentType} = 'text/html'; $Notification{Body} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $Notification{Body}, ); } # do text/html to text/plain convert if ( !$Self->{RichText} && $Notification{ContentType} =~ /text\/html/i ) { $Notification{ContentType} = 'text/plain'; $Notification{Body} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii( String => $Notification{Body}, ); } # get notify texts for my $Text (qw(Subject Body)) { if ( !$Notification{$Text} ) { $Notification{$Text} = "No Notification $Text for $Param{Type} found!"; } } # replace place holder stuff $Notification{Body} = $Self->_Replace( RichText => $Self->{RichText}, Text => $Notification{Body}, Recipient => $Param{Recipient}, Data => $Param{CustomerMessageParams}, DataAgent => \%AgentArticle, TicketData => $Param{TicketData}, UserID => $Param{UserID}, Language => $Language, AddTimezoneInfo => { NotificationEvent => 1, }, ); $Notification{Subject} = $Self->_Replace( RichText => 0, Text => $Notification{Subject}, Recipient => $Param{Recipient}, Data => $Param{CustomerMessageParams}, DataAgent => \%AgentArticle, TicketData => $Param{TicketData}, UserID => $Param{UserID}, Language => $Language, AddTimezoneInfo => { NotificationEvent => 1, }, ); # Keep the "original" (unmodified) subject and body for later use. $Notification{OriginalSubject} = $Notification{Subject}; $Notification{OriginalBody} = $Notification{Body}; $Notification{Subject} = $Kernel::OM->Get('Kernel::System::Ticket')->TicketSubjectBuild( TicketNumber => $Param{TicketData}->{TicketNumber}, Subject => $Notification{Subject} || '', Type => 'New', ); # add URLs and verify to be full HTML document if ( $Self->{RichText} ) { $Notification{Body} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->LinkQuote( String => $Notification{Body}, ); } return %Notification; } =begin Internal: =cut sub _Replace { my ( $Self, %Param ) = @_; # check needed stuff for (qw(Text RichText Data UserID)) { if ( !defined $Param{$_} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $_!" ); return; } } # check for mailto links # since the subject and body of those mailto links are # uri escaped we have to uri unescape them, replace # possible placeholders and then re-uri escape them $Param{Text} =~ s{ (href="mailto:[^\?]+\?)([^"]+") } { my $MailToHref = $1; my $MailToHrefContent = $2; $MailToHrefContent =~ s{ ((?:subject|body)=)(.+?)("|&) } { my $SubjectOrBodyPrefix = $1; my $SubjectOrBodyContent = $2; my $SubjectOrBodySuffix = $3; my $SubjectOrBodyContentUnescaped = URI::Escape::uri_unescape $SubjectOrBodyContent; my $SubjectOrBodyContentReplaced = $Self->_Replace( %Param, Text => $SubjectOrBodyContentUnescaped, RichText => 0, ); my $SubjectOrBodyContentEscaped = URI::Escape::uri_escape_utf8 $SubjectOrBodyContentReplaced; $SubjectOrBodyPrefix . $SubjectOrBodyContentEscaped . $SubjectOrBodySuffix; }egx; $MailToHref . $MailToHrefContent; }egx; my $Start = '<'; my $End = '>'; if ( $Param{RichText} ) { $Start = '<'; $End = '>'; $Param{Text} =~ s/(\n|\r)//g; } my %Ticket; if ( $Param{TicketData} ) { %Ticket = %{ $Param{TicketData} }; } my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser'); # Determine recipient's timezone if needed. my $RecipientTimeZone; if ( $Param{AddTimezoneInfo} ) { $RecipientTimeZone = $Kernel::OM->Create('Kernel::System::DateTime')->OTRSTimeZoneGet(); my %CustomerUser; if ( IsHashRefWithData( \%Ticket ) && $Ticket{CustomerUserID} ) { %CustomerUser = $CustomerUserObject->CustomerUserDataGet( User => $Ticket{CustomerUserID} ); } my %UserPreferences; if ( $Param{AddTimezoneInfo}->{NotificationEvent} && $Param{Recipient}->{Type} eq 'Agent' ) { %UserPreferences = $Kernel::OM->Get('Kernel::System::User')->GetPreferences( UserID => $Param{Recipient}->{UserID}, ); } elsif ( $Param{AddTimezoneInfo}->{NotificationEvent} && $Param{Recipient}->{Type} eq 'Customer' && $Param{Recipient}->{UserID} ) { %UserPreferences = $CustomerUserObject->GetPreferences( UserID => $Param{Recipient}->{UserID}, ); } elsif ( $Param{AddTimezoneInfo}->{AutoResponse} && $Ticket{CustomerUserID} && IsHashRefWithData( \%CustomerUser ) ) { %UserPreferences = $CustomerUserObject->GetPreferences( UserID => $Ticket{CustomerUserID}, ); } if ( $UserPreferences{UserTimeZone} ) { $RecipientTimeZone = $UserPreferences{UserTimeZone}; } } # Replace Unix time format tags. # If language is defined, they will be converted into a correct format in below IF statement. for my $UnixFormatTime ( qw(RealTillTimeNotUsed EscalationResponseTime EscalationUpdateTime EscalationSolutionTime) ) { if ( $Ticket{$UnixFormatTime} ) { $Ticket{$UnixFormatTime} = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { Epoch => $Ticket{$UnixFormatTime}, }, )->ToString(); } } # translate ticket values if needed if ( $Param{Language} ) { my $LanguageObject = Kernel::Language->new( UserLanguage => $Param{Language}, ); # Translate the different values. for my $Field (qw(Type State StateType Lock Priority)) { $Ticket{$Field} = $LanguageObject->Translate( $Ticket{$Field} ); } # Transform the date values from the ticket data (but not the dynamic field values). ATTRIBUTE: for my $Attribute ( sort keys %Ticket ) { next ATTRIBUTE if $Attribute =~ m{ \A DynamicField_ }xms; next ATTRIBUTE if !$Ticket{$Attribute}; if ( $Ticket{$Attribute} =~ m{\A(\d\d\d\d)-(\d\d)-(\d\d)\s(\d\d):(\d\d):(\d\d)\z}xi ) { # Change time to recipient's timezone if needed # and later append timezone information. # For more information, # see bug#13865 (https://bugs.otrs.org/show_bug.cgi?id=13865) # and bug#14270 (https://bugs.otrs.org/show_bug.cgi?id=14270). if ($RecipientTimeZone) { my $DateTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Ticket{$Attribute}, }, ); $DateTimeObject->ToTimeZone( TimeZone => $RecipientTimeZone ); $Ticket{$Attribute} = $DateTimeObject->ToString(); } $Ticket{$Attribute} = $LanguageObject->FormatTimeString( $Ticket{$Attribute}, 'DateFormat', 'NoSeconds', ); # Append timezone information if needed. if ($RecipientTimeZone) { $Ticket{$Attribute} .= " ($RecipientTimeZone)"; } } } my $LocalLayoutObject = Kernel::Output::HTML::Layout->new( Lang => $Param{Language}, ); # Convert tags in seconds to more readable appropriate format if language is defined. for my $TimeInSeconds ( qw(UntilTime EscalationTimeWorkingTime EscalationTime FirstResponseTimeWorkingTime FirstResponseTime UpdateTimeWorkingTime UpdateTime SolutionTimeWorkingTime SolutionTime) ) { if ( $Ticket{$TimeInSeconds} ) { $Ticket{$TimeInSeconds} = $LocalLayoutObject->CustomerAge( Age => $Ticket{$TimeInSeconds}, Space => ' ' ); } } } my %Queue; if ( $Param{QueueID} ) { %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( ID => $Param{QueueID}, ); } my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); # Replace config options. my $Tag = $Start . 'OTRS_CONFIG_'; $Param{Text} =~ s{$Tag(.+?)$End}{ my $Key = $1; my $Value = $ConfigObject->Get($Key) // ''; # Mask sensitive config options. my $Replace = $Self->_MaskSensitiveValue( Key => $Key, Value => $Value, IsConfig => 1, ); $Replace; }egx; # cleanup $Param{Text} =~ s/$Tag.+?$End/-/gi; my %Recipient = %{ $Param{Recipient} || {} }; # get user object my $UserObject = $Kernel::OM->Get('Kernel::System::User'); if ( !%Recipient && $Param{RecipientID} ) { %Recipient = $UserObject->GetUserData( UserID => $Param{RecipientID}, NoOutOfOffice => 1, ); } my $HashGlobalReplace = sub { my ( $Tag, %H ) = @_; # Generate one single matching string for all keys to save performance. my $Keys = join '|', map {quotemeta} grep { defined $H{$_} } keys %H; # Set all keys as lowercase to be able to match case insensitive, # e. g. and . # Also mask any values containing sensitive data. %H = map { lc $_ => $Self->_MaskSensitiveValue( Key => $_, Value => $H{$_}, ) } sort keys %H; # If tag is 'OTRS_CUSTOMER_' add the body alias 'email/note' to be replaced. if ( $Tag =~ m/OTRS_(CUSTOMER|AGENT)_/ ) { KEY: for my $Key (qw( email note )) { my $Value = $H{$Key}; next KEY if defined($Value); $H{$Key} = $H{'body'}; $Keys .= '|' . ucfirst $Key; } } $Param{Text} =~ s/(?:$Tag)($Keys)$End/$H{ lc $1 }/ieg; }; # get recipient data and replace it with Get('Kernel::System::HTMLUtils')->ToHTML( String => $Recipient{$Attribute}, ); } } $HashGlobalReplace->( "$Tag|$RecipientTag", %Recipient ); } # cleanup $Param{Text} =~ s/$RecipientTag.+?$End/-/gi; # get owner data and replace it with GetUserData( UserID => $Ticket{OwnerID}, NoOutOfOffice => 1, ); # html quoting of content if ( $Param{RichText} ) { ATTRIBUTE: for my $Attribute ( sort keys %Owner ) { next ATTRIBUTE if !$Owner{$Attribute}; $Owner{$Attribute} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $Owner{$Attribute}, ); } } $HashGlobalReplace->( "$Tag|$OwnerTag", %Owner ); } # cleanup $Param{Text} =~ s/$Tag.+?$End/-/gi; $Param{Text} =~ s/$OwnerTag.+?$End/-/gi; # get owner data and replace it with GetUserData( UserID => $Ticket{ResponsibleID}, NoOutOfOffice => 1, ); # HTML quoting of content if ( $Param{RichText} ) { ATTRIBUTE: for my $Attribute ( sort keys %Responsible ) { next ATTRIBUTE if !$Responsible{$Attribute}; $Responsible{$Attribute} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $Responsible{$Attribute}, ); } } $HashGlobalReplace->( "$Tag|$ResponsibleTag", %Responsible ); } # cleanup $Param{Text} =~ s/$Tag.+?$End/-/gi; $Param{Text} =~ s/$ResponsibleTag.+?$End/-/gi; $Tag = $Start . 'OTRS_Agent_'; my $Tag2 = $Start . 'OTRS_CURRENT_'; my %CurrentUser = $UserObject->GetUserData( UserID => $Param{UserID}, NoOutOfOffice => 1, ); # HTML quoting of content if ( $Param{RichText} ) { ATTRIBUTE: for my $Attribute ( sort keys %CurrentUser ) { next ATTRIBUTE if !$CurrentUser{$Attribute}; $CurrentUser{$Attribute} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $CurrentUser{$Attribute}, ); } } $HashGlobalReplace->( "$Tag|$Tag2", %CurrentUser ); # replace other needed stuff $Param{Text} =~ s/$Start OTRS_FIRST_NAME $End/$CurrentUser{UserFirstname}/gxms; $Param{Text} =~ s/$Start OTRS_LAST_NAME $End/$CurrentUser{UserLastname}/gxms; # cleanup $Param{Text} =~ s/$Tag2.+?$End/-/gi; # ticket data $Tag = $Start . 'OTRS_TICKET_'; # html quoting of content if ( $Param{RichText} ) { ATTRIBUTE: for my $Attribute ( sort keys %Ticket ) { next ATTRIBUTE if !$Ticket{$Attribute}; $Ticket{$Attribute} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $Ticket{$Attribute}, ); } } # Dropdown, Checkbox and MultipleSelect DynamicFields, can store values (keys) that are # different from the the values to display # returns the stored key # returns the display value my %DynamicFields; # For systems with many Dynamic fields we do not want to load them all unless needed # Find what Dynamic Field Values are requested while ( $Param{Text} =~ m/$Tag DynamicField_(\S+?)(_Value)? $End/gixms ) { $DynamicFields{$1} = 1; } # to store all the required DynamicField display values my %DynamicFieldDisplayValues; # get dynamic field objects my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); # get the dynamic fields for ticket object my $DynamicFieldList = $DynamicFieldObject->DynamicFieldListGet( Valid => 1, ObjectType => ['Ticket'], ) || []; # cycle through the activated Dynamic Fields for this screen DYNAMICFIELD: for my $DynamicFieldConfig ( @{$DynamicFieldList} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); # we only load the ones requested next DYNAMICFIELD if !$DynamicFields{ $DynamicFieldConfig->{Name} }; my $LanguageObject; # translate values if needed if ( $Param{Language} ) { $LanguageObject = Kernel::Language->new( UserLanguage => $Param{Language}, ); } my $DateTimeObject; # Change DateTime DF value for ticket if needed. if ( defined $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } && $DynamicFieldConfig->{FieldType} eq 'DateTime' && $RecipientTimeZone ) { $DateTimeObject = $Kernel::OM->Create( 'Kernel::System::DateTime', ObjectParams => { String => $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} }, }, ); $DateTimeObject->ToTimeZone( TimeZone => $RecipientTimeZone ); $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $DateTimeObject->ToString(); } # get the display value for each dynamic field my $DisplayValue = $DynamicFieldBackendObject->ValueLookup( DynamicFieldConfig => $DynamicFieldConfig, Key => $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} }, LanguageObject => $LanguageObject, ); # get the readable value (value) for each dynamic field my $DisplayValueStrg = $DynamicFieldBackendObject->ReadableValueRender( DynamicFieldConfig => $DynamicFieldConfig, Value => $DisplayValue, ); # fill the DynamicFielsDisplayValues if ($DisplayValueStrg) { $DynamicFieldDisplayValues{ 'DynamicField_' . $DynamicFieldConfig->{Name} . '_Value' } = $DisplayValueStrg->{Value}; # Add timezone info if needed. if ( defined $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } && length $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } && $DynamicFieldConfig->{FieldType} eq 'DateTime' && $RecipientTimeZone ) { $DynamicFieldDisplayValues{ 'DynamicField_' . $DynamicFieldConfig->{Name} . '_Value' } .= " ($RecipientTimeZone)"; } } # get the readable value (key) for each dynamic field my $ValueStrg = $DynamicFieldBackendObject->ReadableValueRender( DynamicFieldConfig => $DynamicFieldConfig, Value => $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} }, ); # replace ticket content with the value from ReadableValueRender (if any) if ( IsHashRefWithData($ValueStrg) ) { $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $ValueStrg->{Value}; # Add timezone info if needed. if ( defined $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } && length $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } && $DynamicFieldConfig->{FieldType} eq 'DateTime' && $RecipientTimeZone ) { $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } .= " ($RecipientTimeZone)"; } } } # replace it $HashGlobalReplace->( $Tag, %Ticket, %DynamicFieldDisplayValues ); # COMPAT $Param{Text} =~ s/$Start OTRS_TICKET_ID $End/$Ticket{TicketID}/gixms; $Param{Text} =~ s/$Start OTRS_TICKET_NUMBER $End/$Ticket{TicketNumber}/gixms; if ( $Ticket{TicketID} ) { $Param{Text} =~ s/$Start OTRS_QUEUE $End/$Ticket{Queue}/gixms; } if ( $Param{QueueID} ) { $Param{Text} =~ s/$Start OTRS_TICKET_QUEUE $End/$Queue{Name}/gixms; } # cleanup $Param{Text} =~ s/$Tag.+?$End/-/gi; # get customer and agent params and replace it with $Param{Data} || {}, 'OTRS_AGENT_' => $Param{DataAgent} || {}, ); # use a list to get customer first for my $DataType (qw(OTRS_CUSTOMER_ OTRS_AGENT_)) { my %Data = %{ $ArticleData{$DataType} }; # HTML quoting of content if ( $Param{RichText} ) { ATTRIBUTE: for my $Attribute ( sort keys %Data ) { next ATTRIBUTE if !$Data{$Attribute}; $Data{$Attribute} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $Data{$Attribute}, ); } } if (%Data) { # replace and tags $Tag = $Start . $DataType; $HashGlobalReplace->( $Tag, %Data ); # prepare body (insert old email) , # , ..., # Changed this to a 'while' to allow the same key/tag multiple times and different number of lines. while ( $Param{Text} =~ /$Start(?:$DataType(EMAIL|NOTE|BODY)\[(.+?)\])$End/ || $Param{Text} =~ /$Start(?:OTRS_COMMENT(\[(.+?)\])?)$End/ ) { my $Line = $2 || 2500; my $NewOldBody = ''; my @Body = split( /\n/, $Data{Body} ); for my $Counter ( 0 .. $Line - 1 ) { # 2002-06-14 patch of Pablo Ruiz Garcia # http://lists.otrs.org/pipermail/dev/2002-June/000012.html if ( $#Body >= $Counter ) { # add no quote char, do it later by using DocumentCleanup() if ( $Param{RichText} ) { $NewOldBody .= $Body[$Counter]; } # add "> " as quote char else { $NewOldBody .= "> $Body[$Counter]"; } # add new line if ( $Counter < ( $Line - 1 ) ) { $NewOldBody .= "\n"; } } $Counter++; } chomp $NewOldBody; # HTML quoting of content if ( $Param{RichText} && $NewOldBody ) { # remove trailing new lines for ( 1 .. 10 ) { $NewOldBody =~ s/()\s{0,20}$//gs; } # add quote $NewOldBody = "
$NewOldBody
"; $NewOldBody = $Kernel::OM->Get('Kernel::System::HTMLUtils')->DocumentCleanup( String => $NewOldBody, ); } # replace tag $Param{Text} =~ s/$Start(?:(?:$DataType(EMAIL|NOTE|BODY)\[(.+?)\]|(?:OTRS_COMMENT(\[(.+?)\])?)))$End/$NewOldBody/; } # replace and tags $Tag = "$Start$DataType" . 'SUBJECT'; if ( $Param{Text} =~ /$Tag\[(.+?)\]$End/g ) { my $SubjectChar = $1; my $Subject = $Kernel::OM->Get('Kernel::System::Ticket')->TicketSubjectClean( TicketNumber => $Ticket{TicketNumber}, Subject => $Data{Subject}, ); $Subject =~ s/^(.{$SubjectChar}).*$/$1 [...]/; $Param{Text} =~ s/$Tag\[.+?\]$End/$Subject/g; } if ( $DataType eq 'OTRS_CUSTOMER_' ) { # Arnold Ligtvoet - otrs@ligtvoet.org # get from body and replace with received date use POSIX qw(strftime); $Tag = $Start . 'OTRS_EMAIL_DATE'; if ( $Param{Text} =~ /$Tag\[(.+?)\]$End/g ) { my $TimeZone = $1; my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); my $EmailDate = $DateTimeObject->Format( Format => '%A, %B %e, %Y at %T ' ); $EmailDate .= "($TimeZone)"; $Param{Text} =~ s/$Tag\[.+?\]$End/$EmailDate/g; } } } if ( $DataType eq 'OTRS_CUSTOMER_' ) { # get and prepare realname $Tag = $Start . 'OTRS_CUSTOMER_REALNAME'; if ( $Param{Text} =~ /$Tag$End/i ) { my $From; if ( $Ticket{CustomerUserID} ) { $From = $CustomerUserObject->CustomerName( UserLogin => $Ticket{CustomerUserID} ); } # try to get the real name directly from the data $From //= $Recipient{Realname}; # get real name based on reply-to if ( !$From && $Data{ReplyTo} ) { $From = $Data{ReplyTo}; # remove email addresses $From =~ s/<.*>|<.*>|\(.*\)|\"|"|;|,//g; # remove leading/trailing spaces $From =~ s/^\s+//g; $From =~ s/\s+$//g; } # generate real name based on sender line if ( !$From ) { $From = $Data{To} || ''; # remove email addresses $From =~ s/<.*>|<.*>|\(.*\)|\"|"|;|,//g; # remove leading/trailing spaces $From =~ s/^\s+//g; $From =~ s/\s+$//g; } # replace with from $Param{Text} =~ s/$Tag$End/$From/g; } } } # get customer data and replace it with {CustomerUserID} ) { my $CustomerUserID = $Param{Data}->{CustomerUserID} || $Ticket{CustomerUserID}; my %CustomerUser = $CustomerUserObject->CustomerUserDataGet( User => $CustomerUserID, ); # HTML quoting of content if ( $Param{RichText} ) { ATTRIBUTE: for my $Attribute ( sort keys %CustomerUser ) { next ATTRIBUTE if !$CustomerUser{$Attribute}; $CustomerUser{$Attribute} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML( String => $CustomerUser{$Attribute}, ); } } # replace it $HashGlobalReplace->( "$Tag|$Tag2", %CustomerUser ); } # cleanup all not needed _RemoveUnSupportedTag( Text => $SomeTextWithTags, ListOfUnSupportedTag => \@ListOfUnSupportedTag, ); =cut sub _RemoveUnSupportedTag { my ( $Self, %Param ) = @_; # check needed stuff for my $Item (qw(Text ListOfUnSupportedTag)) { if ( !defined $Param{$Item} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Item!" ); return; } } my $Start = '<'; my $End = '>'; if ( $Self->{RichText} ) { $Start = '<'; $End = '>'; $Param{Text} =~ s/(\n|\r)//g; } # Cleanup all not supported tags with and without number, e.g. OTRS_CUSTOMER_BODY and OTRS_CUSTOMER_BODY[n]. # See https://bugs.otrs.org/show_bug.cgi?id=14369 and https://bugs.otrs.org/show_bug.cgi?id=10825. my $NotSupportedTag = $Start . "(?:" . join( "|", @{ $Param{ListOfUnSupportedTag} } ) . ")(\\[.*?\\])?" . $End; $Param{Text} =~ s/$NotSupportedTag/-/gi; return $Param{Text}; } =head2 _MaskSensitiveValue() Mask sensitive value, i.e. a password, a security token, etc. my $MaskedValue = $Self->_MaskSensitiveValue( Key => 'DatabasePassword', # (required) Name of the field/key. Value => 'secretvalue', # (optional) Value to potentially mask. IsConfig => 1, # (optional) Whether the value is a config option, default: 0. ); Returns masked value, in case the key is matched: $MaskedValue = 'xxx'; =cut sub _MaskSensitiveValue { my ( $Self, %Param ) = @_; return '' if !$Param{Key} || !defined $Param{Value}; # Skip masking sensitive values for Dynamic Fields. return $Param{Value} if $Param{Key} =~ qr{ dynamicfield }xi; # Match general key names, i.e. from the user preferences. my $Match = qr{ config|secret|passw|userpw|auth|token }xi; # Match forbidden config keys. if ( $Param{IsConfig} ) { $Match = qr{ (?:password|pw) \d* $ }smxi; } return $Param{Value} if $Param{Key} !~ $Match; return 'xxx'; } 1; =end Internal: =head1 TERMS AND CONDITIONS This software is part of the OTRS project (L). This software comes with ABSOLUTELY NO WARRANTY. For details, see the enclosed file COPYING for license information (GPL). If you did not receive this file, see L. =cut