init III
This commit is contained in:
572
Perl OTRS/Kernel/Output/HTML/Layout/Article.pm
Normal file
572
Perl OTRS/Kernel/Output/HTML/Layout/Article.pm
Normal file
@@ -0,0 +1,572 @@
|
||||
# --
|
||||
# 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::Output::HTML::Layout::Article;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $ObjectManagerDisabled = 1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::Output::HTML::Layout::Article - Helper functions for article rendering.
|
||||
|
||||
=head1 PUBLIC INTERFACE
|
||||
|
||||
=head2 ArticleFields()
|
||||
|
||||
Get article fields as returned by specific article backend.
|
||||
|
||||
my %ArticleFields = $LayoutObject->ArticleFields(
|
||||
TicketID => 123, # (required)
|
||||
ArticleID => 123, # (required)
|
||||
);
|
||||
|
||||
Returns article fields hash:
|
||||
|
||||
%ArticleFields = (
|
||||
Sender => { # mandatory
|
||||
Label => 'Sender',
|
||||
Value => 'John Smith',
|
||||
Prio => 100,
|
||||
},
|
||||
Subject => { # mandatory
|
||||
Label => 'Subject',
|
||||
Value => 'Message',
|
||||
Prio => 200,
|
||||
},
|
||||
...
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub ArticleFields {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Check needed stuff.
|
||||
for my $Needed (qw(TicketID ArticleID)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need $Needed!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
my $BackendObject = $Self->_BackendGet(%Param);
|
||||
|
||||
# Return backend response.
|
||||
return $BackendObject->ArticleFields(
|
||||
%Param,
|
||||
);
|
||||
}
|
||||
|
||||
=head2 ArticlePreview()
|
||||
|
||||
Get article content preview as returned by specific article backend.
|
||||
|
||||
my $ArticlePreview = $LayoutObject->ArticlePreview(
|
||||
TicketID => 123, # (required)
|
||||
ArticleID => 123, # (required)
|
||||
ResultType => 'plain', # (optional) plain|HTML, default: HTML
|
||||
MaxLength => 50, # (optional) performs trimming (for plain result only)
|
||||
);
|
||||
|
||||
Returns article preview in scalar form:
|
||||
|
||||
$ArticlePreview = 'Hello, world!';
|
||||
|
||||
=cut
|
||||
|
||||
sub ArticlePreview {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Check needed stuff.
|
||||
for my $Needed (qw(TicketID ArticleID)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need $Needed!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
my $BackendObject = $Self->_BackendGet(%Param);
|
||||
|
||||
# Return backend response.
|
||||
return $BackendObject->ArticlePreview(
|
||||
%Param,
|
||||
);
|
||||
}
|
||||
|
||||
=head2 ArticleActions()
|
||||
|
||||
Get available article actions as returned by specific article backend.
|
||||
|
||||
my @Actions = $LayoutObject->ArticleActions(
|
||||
TicketID => 123, # (required)
|
||||
ArticleID => 123, # (required)
|
||||
);
|
||||
|
||||
Returns article action array:
|
||||
|
||||
@Actions = (
|
||||
{
|
||||
ItemType => 'Dropdown',
|
||||
DropdownType => 'Reply',
|
||||
StandardResponsesStrg => $StandardResponsesStrg,
|
||||
Name => 'Reply',
|
||||
Class => 'AsPopup PopupType_TicketAction',
|
||||
Action => 'AgentTicketCompose',
|
||||
FormID => 'Reply' . $Article{ArticleID},
|
||||
ResponseElementID => 'ResponseID',
|
||||
Type => $Param{Type},
|
||||
},
|
||||
{
|
||||
ItemType => 'Link',
|
||||
Description => 'Forward article via mail',
|
||||
Name => 'Forward',
|
||||
Class => 'AsPopup PopupType_TicketAction',
|
||||
Link =>
|
||||
"Action=AgentTicketForward;TicketID=$Ticket{TicketID};ArticleID=$Article{ArticleID}"
|
||||
},
|
||||
...
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub ArticleActions {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Check needed stuff.
|
||||
for my $Needed (qw(TicketID ArticleID)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need $Needed!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
my $BackendObject = $Self->_BackendGet(%Param);
|
||||
|
||||
# Return backend response.
|
||||
return $BackendObject->ArticleActions(
|
||||
%Param,
|
||||
UserID => $Self->{UserID},
|
||||
);
|
||||
}
|
||||
|
||||
=head2 ArticleCustomerRecipientsGet()
|
||||
|
||||
Get customer users from an article to use as recipients.
|
||||
|
||||
my @CustomerUserIDs = $LayoutObject->ArticleCustomerRecipientsGet(
|
||||
TicketID => 123, # (required)
|
||||
ArticleID => 123, # (required)
|
||||
);
|
||||
|
||||
Returns array of customer user IDs who should receive a message:
|
||||
|
||||
@CustomerUserIDs = (
|
||||
'customer-1',
|
||||
'customer-2',
|
||||
...
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub ArticleCustomerRecipientsGet {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
for my $Needed (qw(TicketID ArticleID)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need $Needed!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
my $BackendObject = $Self->_BackendGet(%Param);
|
||||
|
||||
# Return backend response.
|
||||
return $BackendObject->ArticleCustomerRecipientsGet(
|
||||
%Param,
|
||||
UserID => $Self->{UserID},
|
||||
);
|
||||
}
|
||||
|
||||
=head2 ArticleQuote()
|
||||
|
||||
get body and attach e. g. inline documents and/or attach all attachments to
|
||||
upload cache
|
||||
|
||||
for forward or split, get body and attach all attachments
|
||||
|
||||
my $HTMLBody = $LayoutObject->ArticleQuote(
|
||||
TicketID => 123,
|
||||
ArticleID => 123,
|
||||
FormID => $Self->{FormID},
|
||||
UploadCacheObject => $Self->{UploadCacheObject},
|
||||
AttachmentsInclude => 1,
|
||||
);
|
||||
|
||||
or just for including inline documents to upload cache
|
||||
|
||||
my $HTMLBody = $LayoutObject->ArticleQuote(
|
||||
TicketID => 123,
|
||||
ArticleID => 123,
|
||||
FormID => $Self->{FormID},
|
||||
UploadCacheObject => $Self->{UploadCacheObject},
|
||||
AttachmentsInclude => 0,
|
||||
);
|
||||
|
||||
Both will also work without rich text (if $ConfigObject->Get('Frontend::RichText')
|
||||
is false), return param will be text/plain instead.
|
||||
|
||||
=cut
|
||||
|
||||
sub ArticleQuote {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
for my $Needed (qw(TicketID ArticleID FormID UploadCacheObject)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
$Self->FatalError( Message => "Need $Needed!" );
|
||||
}
|
||||
}
|
||||
|
||||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||||
my $ArticleBackendObject = $ArticleObject->BackendForArticle(
|
||||
ArticleID => $Param{ArticleID},
|
||||
TicketID => $Param{TicketID}
|
||||
);
|
||||
|
||||
# body preparation for plain text processing
|
||||
if ( $ConfigObject->Get('Frontend::RichText') ) {
|
||||
|
||||
my $Body = '';
|
||||
|
||||
my %NotInlineAttachments;
|
||||
|
||||
my %QuoteArticle = $ArticleBackendObject->ArticleGet(
|
||||
TicketID => $Param{TicketID},
|
||||
ArticleID => $Param{ArticleID},
|
||||
DynamicFields => 0,
|
||||
);
|
||||
|
||||
# Get the attachments without message bodies.
|
||||
$QuoteArticle{Atms} = {
|
||||
$ArticleBackendObject->ArticleAttachmentIndex(
|
||||
ArticleID => $Param{ArticleID},
|
||||
ExcludePlainText => 1,
|
||||
ExcludeHTMLBody => 1,
|
||||
)
|
||||
};
|
||||
|
||||
# Check if there is HTML body attachment.
|
||||
my %AttachmentIndexHTMLBody = $ArticleBackendObject->ArticleAttachmentIndex(
|
||||
ArticleID => $Param{ArticleID},
|
||||
OnlyHTMLBody => 1,
|
||||
);
|
||||
my ($HTMLBodyAttachmentID) = sort keys %AttachmentIndexHTMLBody;
|
||||
|
||||
if ($HTMLBodyAttachmentID) {
|
||||
my %AttachmentHTML = $ArticleBackendObject->ArticleAttachment(
|
||||
TicketID => $QuoteArticle{TicketID},
|
||||
ArticleID => $QuoteArticle{ArticleID},
|
||||
FileID => $HTMLBodyAttachmentID,
|
||||
);
|
||||
my $Charset = $AttachmentHTML{ContentType} || '';
|
||||
$Charset =~ s/.+?charset=("|'|)(\w+)/$2/gi;
|
||||
$Charset =~ s/"|'//g;
|
||||
$Charset =~ s/(.+?);.*/$1/g;
|
||||
|
||||
# convert html body to correct charset
|
||||
$Body = $Kernel::OM->Get('Kernel::System::Encode')->Convert(
|
||||
Text => $AttachmentHTML{Content},
|
||||
From => $Charset,
|
||||
To => $Self->{UserCharset},
|
||||
Check => 1,
|
||||
);
|
||||
|
||||
# get HTML utils object
|
||||
my $HTMLUtilsObject = $Kernel::OM->Get('Kernel::System::HTMLUtils');
|
||||
|
||||
# add url quoting
|
||||
$Body = $HTMLUtilsObject->LinkQuote(
|
||||
String => $Body,
|
||||
);
|
||||
|
||||
# strip head, body and meta elements
|
||||
$Body = $HTMLUtilsObject->DocumentStrip(
|
||||
String => $Body,
|
||||
);
|
||||
|
||||
# display inline images if exists
|
||||
my $SessionID = '';
|
||||
if ( $Self->{SessionID} && !$Self->{SessionIDCookie} ) {
|
||||
$SessionID = ';' . $Self->{SessionName} . '=' . $Self->{SessionID};
|
||||
}
|
||||
my $AttachmentLink = $Self->{Baselink}
|
||||
. 'Action=PictureUpload'
|
||||
. ';FormID='
|
||||
. $Param{FormID}
|
||||
. $SessionID
|
||||
. ';ContentID=';
|
||||
|
||||
# search inline documents in body and add it to upload cache
|
||||
my %Attachments = %{ $QuoteArticle{Atms} };
|
||||
my %AttachmentAlreadyUsed;
|
||||
$Body =~ s{
|
||||
(=|"|')cid:(.*?)("|'|>|\/>|\s)
|
||||
}
|
||||
{
|
||||
my $Start= $1;
|
||||
my $ContentID = $2;
|
||||
my $End = $3;
|
||||
|
||||
# improve html quality
|
||||
if ( $Start ne '"' && $Start ne '\'' ) {
|
||||
$Start .= '"';
|
||||
}
|
||||
if ( $End ne '"' && $End ne '\'' ) {
|
||||
$End = '"' . $End;
|
||||
}
|
||||
|
||||
# find attachment to include
|
||||
ATMCOUNT:
|
||||
for my $AttachmentID ( sort keys %Attachments ) {
|
||||
|
||||
if ( lc $Attachments{$AttachmentID}->{ContentID} ne lc "<$ContentID>" ) {
|
||||
next ATMCOUNT;
|
||||
}
|
||||
|
||||
# get whole attachment
|
||||
my %AttachmentPicture = $ArticleBackendObject->ArticleAttachment(
|
||||
TicketID => $Param{TicketID},
|
||||
ArticleID => $Param{ArticleID},
|
||||
FileID => $AttachmentID,
|
||||
);
|
||||
|
||||
# content id cleanup
|
||||
$AttachmentPicture{ContentID} =~ s/^<//;
|
||||
$AttachmentPicture{ContentID} =~ s/>$//;
|
||||
|
||||
# find cid, add attachment URL and remember, file is already uploaded
|
||||
$ContentID = $AttachmentLink . $Self->LinkEncode( $AttachmentPicture{ContentID} );
|
||||
|
||||
# add to upload cache if not uploaded and remember
|
||||
if (!$AttachmentAlreadyUsed{$AttachmentID}) {
|
||||
|
||||
# remember
|
||||
$AttachmentAlreadyUsed{$AttachmentID} = 1;
|
||||
|
||||
# write attachment to upload cache
|
||||
$Param{UploadCacheObject}->FormIDAddFile(
|
||||
FormID => $Param{FormID},
|
||||
Disposition => 'inline',
|
||||
%{ $Attachments{$AttachmentID} },
|
||||
%AttachmentPicture,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# return link
|
||||
$Start . $ContentID . $End;
|
||||
}egxi;
|
||||
|
||||
# find inline images using Content-Location instead of Content-ID
|
||||
ATTACHMENT:
|
||||
for my $AttachmentID ( sort keys %Attachments ) {
|
||||
|
||||
next ATTACHMENT if !$Attachments{$AttachmentID}->{ContentID};
|
||||
|
||||
# get whole attachment
|
||||
my %AttachmentPicture = $ArticleBackendObject->ArticleAttachment(
|
||||
TicketID => $Param{TicketID},
|
||||
ArticleID => $Param{ArticleID},
|
||||
FileID => $AttachmentID,
|
||||
);
|
||||
|
||||
# content id cleanup
|
||||
$AttachmentPicture{ContentID} =~ s/^<//;
|
||||
$AttachmentPicture{ContentID} =~ s/>$//;
|
||||
|
||||
$Body =~ s{
|
||||
("|')(\Q$AttachmentPicture{ContentID}\E)("|'|>|\/>|\s)
|
||||
}
|
||||
{
|
||||
my $Start= $1;
|
||||
my $ContentID = $2;
|
||||
my $End = $3;
|
||||
|
||||
# find cid, add attachment URL and remember, file is already uploaded
|
||||
$ContentID = $AttachmentLink . $Self->LinkEncode( $AttachmentPicture{ContentID} );
|
||||
|
||||
# add to upload cache if not uploaded and remember
|
||||
if (!$AttachmentAlreadyUsed{$AttachmentID}) {
|
||||
|
||||
# remember
|
||||
$AttachmentAlreadyUsed{$AttachmentID} = 1;
|
||||
|
||||
# write attachment to upload cache
|
||||
$Param{UploadCacheObject}->FormIDAddFile(
|
||||
FormID => $Param{FormID},
|
||||
Disposition => 'inline',
|
||||
%{ $Attachments{$AttachmentID} },
|
||||
%AttachmentPicture,
|
||||
);
|
||||
}
|
||||
|
||||
# return link
|
||||
$Start . $ContentID . $End;
|
||||
}egxi;
|
||||
}
|
||||
|
||||
# find not inline images
|
||||
ATTACHMENT:
|
||||
for my $AttachmentID ( sort keys %Attachments ) {
|
||||
next ATTACHMENT if $AttachmentAlreadyUsed{$AttachmentID};
|
||||
$NotInlineAttachments{$AttachmentID} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
# attach also other attachments on article forward
|
||||
if ( $Body && $Param{AttachmentsInclude} ) {
|
||||
for my $AttachmentID ( sort keys %NotInlineAttachments ) {
|
||||
my %Attachment = $ArticleBackendObject->ArticleAttachment(
|
||||
TicketID => $Param{TicketID},
|
||||
ArticleID => $Param{ArticleID},
|
||||
FileID => $AttachmentID,
|
||||
);
|
||||
|
||||
# add attachment
|
||||
$Param{UploadCacheObject}->FormIDAddFile(
|
||||
FormID => $Param{FormID},
|
||||
%Attachment,
|
||||
Disposition => 'attachment',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# Fallback for non-MIMEBase articles: get article HTML content if it exists.
|
||||
if ( !$Body ) {
|
||||
$Body = $Self->ArticlePreview(
|
||||
TicketID => $Param{TicketID},
|
||||
ArticleID => $Param{ArticleID},
|
||||
);
|
||||
}
|
||||
|
||||
return $Body if $Body;
|
||||
}
|
||||
|
||||
# as fallback use text body for quote
|
||||
my %Article = $ArticleBackendObject->ArticleGet(
|
||||
TicketID => $Param{TicketID},
|
||||
ArticleID => $Param{ArticleID},
|
||||
DynamicFields => 0,
|
||||
);
|
||||
|
||||
# check if original content isn't text/plain or text/html, don't use it
|
||||
if ( !$Article{ContentType} ) {
|
||||
$Article{ContentType} = 'text/plain';
|
||||
}
|
||||
|
||||
if ( $Article{ContentType} !~ /text\/(plain|html)/i ) {
|
||||
$Article{Body} = '-> no quotable message <-';
|
||||
$Article{ContentType} = 'text/plain';
|
||||
}
|
||||
else {
|
||||
$Article{Body} = $Self->WrapPlainText(
|
||||
MaxCharacters => $ConfigObject->Get('Ticket::Frontend::TextAreaEmail') || 82,
|
||||
PlainText => $Article{Body},
|
||||
);
|
||||
}
|
||||
|
||||
# attach attachments
|
||||
if ( $Param{AttachmentsInclude} ) {
|
||||
my %ArticleIndex = $ArticleBackendObject->ArticleAttachmentIndex(
|
||||
ArticleID => $Param{ArticleID},
|
||||
ExcludePlainText => 1,
|
||||
ExcludeHTMLBody => 1,
|
||||
);
|
||||
for my $Index ( sort keys %ArticleIndex ) {
|
||||
my %Attachment = $ArticleBackendObject->ArticleAttachment(
|
||||
TicketID => $Param{TicketID},
|
||||
ArticleID => $Param{ArticleID},
|
||||
FileID => $Index,
|
||||
);
|
||||
|
||||
# add attachment
|
||||
$Param{UploadCacheObject}->FormIDAddFile(
|
||||
FormID => $Param{FormID},
|
||||
%Attachment,
|
||||
Disposition => 'attachment',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# return body as html
|
||||
if ( $ConfigObject->Get('Frontend::RichText') ) {
|
||||
|
||||
$Article{Body} = $Self->Ascii2Html(
|
||||
Text => $Article{Body},
|
||||
HTMLResultMode => 1,
|
||||
LinkFeature => 1,
|
||||
);
|
||||
}
|
||||
|
||||
# return body as plain text
|
||||
return $Article{Body};
|
||||
}
|
||||
|
||||
sub _BackendGet {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Check needed stuff.
|
||||
for my $Needed (qw(TicketID ArticleID)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need $Needed!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
my $ArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle(%Param);
|
||||
|
||||
# Determine channel name for this article.
|
||||
my $ChannelName = $ArticleBackendObject->ChannelNameGet();
|
||||
|
||||
my $Loaded = $Kernel::OM->Get('Kernel::System::Main')->Require(
|
||||
"Kernel::Output::HTML::Article::$ChannelName",
|
||||
);
|
||||
return if !$Loaded;
|
||||
|
||||
return $Kernel::OM->Get("Kernel::Output::HTML::Article::$ChannelName");
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 TERMS AND CONDITIONS
|
||||
|
||||
This software is part of the OTRS project (L<https://otrs.org/>).
|
||||
|
||||
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<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user