Files
2024-10-14 00:08:40 +02:00

358 lines
10 KiB
Perl

# --
# 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::Ticket::Article::Backend::MIMEBase::Base;
use strict;
use warnings;
our $ObjectManagerDisabled = 1;
=head1 NAME
Kernel::System::Ticket::Article::Backend::MIMEBase::Base - base class for article storage modules
=head1 DESCRIPTION
This is a base class for article storage backends and should not be instantiated directly.
=head1 PUBLIC INTERFACE
=cut
=head2 new()
Don't instantiate this class directly, get instances of the real storage backends instead:
my $BackendObject = $Kernel::OM->Get('Kernel::System::Article::Backend::MIMEBase::ArticleStorageDB');
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
$Self->{CacheType} = 'ArticleStorageBase';
$Self->{CacheTTL} = 60 * 60 * 24 * 20;
$Self->{ArticleDataDir}
= $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Article::Backend::MIMEBase::ArticleDataDir')
|| die 'Got no ArticleDataDir!';
# do we need to check all backends, or just one?
$Self->{CheckAllBackends}
= $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Article::Backend::MIMEBase::CheckAllStorageBackends')
// 0;
return $Self;
}
=head2 BuildArticleContentPath()
Generate a base article content path for article storage in the file system.
my $ArticleContentPath = $BackendObject->BuildArticleContentPath();
=cut
sub BuildArticleContentPath {
my ( $Self, %Param ) = @_;
return $Self->{ArticleContentPath} if $Self->{ArticleContentPath};
$Self->{ArticleContentPath} = $Kernel::OM->Create('Kernel::System::DateTime')->Format(
Format => '%Y/%m/%d',
);
return $Self->{ArticleContentPath};
}
=head2 ArticleAttachmentIndex()
Get article attachment index as hash.
my %Index = $BackendObject->ArticleAttachmentIndex(
ArticleID => 123,
ExcludePlainText => 1, # (optional) Exclude plain text attachment
ExcludeHTMLBody => 1, # (optional) Exclude HTML body attachment
ExcludeInline => 1, # (optional) Exclude inline attachments
OnlyHTMLBody => 1, # (optional) Return only HTML body attachment, return nothing if not found
);
Returns:
my %Index = {
'1' => { # Attachment ID
ContentAlternative => '', # (optional)
ContentID => '', # (optional)
ContentType => 'application/pdf',
Filename => 'StdAttachment-Test1.pdf',
FilesizeRaw => 4722,
Disposition => 'attachment',
},
'2' => {
ContentAlternative => '',
ContentID => '',
ContentType => 'text/html; charset="utf-8"',
Filename => 'file-2',
FilesizeRaw => 183,
Disposition => 'attachment',
},
...
};
=cut
sub ArticleAttachmentIndex {
my ( $Self, %Param ) = @_;
if ( !$Param{ArticleID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need ArticleID!',
);
return;
}
if ( $Param{ExcludeHTMLBody} && $Param{OnlyHTMLBody} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'ExcludeHTMLBody and OnlyHTMLBody cannot be used together!',
);
return;
}
# Get complete attachment index from backend.
my %Attachments = $Self->ArticleAttachmentIndexRaw(%Param);
# Iterate over attachments only if any of optional parameters is active.
if ( $Param{ExcludePlainText} || $Param{ExcludeHTMLBody} || $Param{ExcludeInline} || $Param{OnlyHTMLBody} ) {
my $AttachmentIDPlain = 0;
my $AttachmentIDHTML = 0;
ATTACHMENT_ID:
for my $AttachmentID ( sort keys %Attachments ) {
my %File = %{ $Attachments{$AttachmentID} };
# Identify plain text attachment.
if (
!$AttachmentIDPlain
&&
$File{Filename} eq 'file-1'
&& $File{ContentType} =~ /text\/plain/i
&& $File{Disposition} eq 'inline'
)
{
$AttachmentIDPlain = $AttachmentID;
next ATTACHMENT_ID;
}
# Identify html body attachment:
# - file-[12] is plain+html attachment
# - file-1.html is html attachment only
if (
!$AttachmentIDHTML
&&
( $File{Filename} =~ /^file-[12]$/ || $File{Filename} eq 'file-1.html' )
&& $File{ContentType} =~ /text\/html/i
&& $File{Disposition} eq 'inline'
)
{
$AttachmentIDHTML = $AttachmentID;
next ATTACHMENT_ID;
}
}
# If neither plain text or html body were found, iterate again to try to identify plain text among regular
# non-inline attachments.
if ( !$AttachmentIDPlain && !$AttachmentIDHTML ) {
ATTACHMENT_ID:
for my $AttachmentID ( sort keys %Attachments ) {
my %File = %{ $Attachments{$AttachmentID} };
# Remember, file-1 got defined by parsing if no filename was given.
if (
$File{Filename} eq 'file-1'
&& $File{ContentType} =~ /text\/plain/i
)
{
$AttachmentIDPlain = $AttachmentID;
last ATTACHMENT_ID;
}
}
}
# Identify inline (image) attachments which are referenced in HTML body. Do not strip attachments based on their
# disposition, since this method of detection is unreliable. Please see bug#13353 for more information.
my @AttachmentIDsInline;
if ($AttachmentIDHTML) {
# Get HTML article body.
my %HTMLBody = $Self->ArticleAttachment(
ArticleID => $Param{ArticleID},
FileID => $AttachmentIDHTML,
);
if ( %HTMLBody && $HTMLBody{Content} ) {
ATTACHMENT_ID:
for my $AttachmentID ( sort keys %Attachments ) {
my %File = %{ $Attachments{$AttachmentID} };
next ATTACHMENT_ID if $File{ContentType} !~ m{image}ixms;
next ATTACHMENT_ID if !$File{ContentID};
my ($ImageID) = ( $File{ContentID} =~ m{^<(.*)>$}ixms );
# Search in the article body if there is any reference to it.
if ( $HTMLBody{Content} =~ m{<img.+src=['|"]cid:\Q$ImageID\E['|"].*>}ixms ) {
push @AttachmentIDsInline, $AttachmentID;
}
}
}
}
if ( $AttachmentIDPlain && $Param{ExcludePlainText} ) {
delete $Attachments{$AttachmentIDPlain};
}
if ( $AttachmentIDHTML && $Param{ExcludeHTMLBody} ) {
delete $Attachments{$AttachmentIDHTML};
}
if ( $Param{ExcludeInline} ) {
for my $AttachmentID (@AttachmentIDsInline) {
delete $Attachments{$AttachmentID};
}
}
if ( $Param{OnlyHTMLBody} ) {
if ($AttachmentIDHTML) {
%Attachments = (
$AttachmentIDHTML => $Attachments{$AttachmentIDHTML}
);
}
else {
%Attachments = ();
}
}
}
return %Attachments;
}
=head1 PRIVATE FUNCTIONS
=cut
sub _ArticleDeleteDirectory {
my ( $Self, %Param ) = @_;
for my $Needed (qw(ArticleID UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!"
);
return;
}
}
# delete directory from fs
my $ContentPath = $Self->_ArticleContentPathGet(
ArticleID => $Param{ArticleID},
);
my $Path = "$Self->{ArticleDataDir}/$ContentPath/$Param{ArticleID}";
if ( -d $Path ) {
if ( !rmdir $Path ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Can't remove '$Path': $!.",
);
return;
}
}
return 1;
}
=head2 _ArticleContentPathGet()
Get the stored content path of an article.
my $Path = $BackendObject->_ArticleContentPatGeth(
ArticleID => 123,
);
=cut
sub _ArticleContentPathGet {
my ( $Self, %Param ) = @_;
if ( !$Param{ArticleID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need ArticleID!',
);
return;
}
# check key
my $CacheKey = '_ArticleContentPathGet::' . $Param{ArticleID};
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# check cache
my $Cache = $CacheObject->Get(
Type => $Self->{CacheType},
Key => $CacheKey,
);
return $Cache if $Cache;
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# sql query
return if !$DBObject->Prepare(
SQL => 'SELECT content_path FROM article_data_mime WHERE article_id = ?',
Bind => [ \$Param{ArticleID} ],
);
my $Result;
while ( my @Row = $DBObject->FetchrowArray() ) {
$Result = $Row[0];
}
# set cache
$CacheObject->Set(
Type => $Self->{CacheType},
TTL => $Self->{CacheTTL},
Key => $CacheKey,
Value => $Result,
);
# return
return $Result;
}
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