This commit is contained in:
2024-10-14 00:08:40 +02:00
parent dbfba56f66
commit 1462d52e13
4572 changed files with 2658864 additions and 0 deletions

View File

@@ -0,0 +1,511 @@
# --
# 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::ArticleStorageDB;
use strict;
use warnings;
use MIME::Base64;
use MIME::Words qw(:all);
use parent qw(Kernel::System::Ticket::Article::Backend::MIMEBase::Base);
use Kernel::System::VariableCheck qw(:all);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DB',
'Kernel::System::DynamicFieldValue',
'Kernel::System::Encode',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorageFS',
);
=head1 NAME
Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorageDB - DB based ticket article storage interface
=head1 DESCRIPTION
This class provides functions to manipulate ticket articles in the database.
The methods are currently documented in L<Kernel::System::Ticket::Article::Backend::MIMEBase>.
Inherits from L<Kernel::System::Ticket::Article::Backend::MIMEBase::Base>.
See also L<Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorageFS>.
=cut
sub ArticleDelete {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Item (qw(ArticleID UserID)) {
if ( !$Param{$Item} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Item!"
);
return;
}
}
# delete attachments
$Self->ArticleDeleteAttachment(
ArticleID => $Param{ArticleID},
UserID => $Param{UserID},
);
# delete plain message
$Self->ArticleDeletePlain(
ArticleID => $Param{ArticleID},
UserID => $Param{UserID},
);
# Delete storage directory in case there are leftovers in the FS.
$Self->_ArticleDeleteDirectory(
ArticleID => $Param{ArticleID},
UserID => $Param{UserID},
);
return 1;
}
sub ArticleDeletePlain {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Item (qw(ArticleID UserID)) {
if ( !$Param{$Item} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Item!"
);
return;
}
}
# delete attachments
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => 'DELETE FROM article_data_mime_plain WHERE article_id = ?',
Bind => [ \$Param{ArticleID} ],
);
# return if we only need to check one backend
return 1 if !$Self->{CheckAllBackends};
# return of only delete in my backend
return 1 if $Param{OnlyMyBackend};
return $Kernel::OM->Get('Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorageFS')->ArticleDeletePlain(
%Param,
OnlyMyBackend => 1,
);
}
sub ArticleDeleteAttachment {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Item (qw(ArticleID UserID)) {
if ( !$Param{$Item} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Item!"
);
return;
}
}
# delete attachments
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => 'DELETE FROM article_data_mime_attachment WHERE article_id = ?',
Bind => [ \$Param{ArticleID} ],
);
# return if we only need to check one backend
return 1 if !$Self->{CheckAllBackends};
# return if only delete in my backend
return 1 if $Param{OnlyMyBackend};
return $Kernel::OM->Get('Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorageFS')
->ArticleDeleteAttachment(
%Param,
OnlyMyBackend => 1,
);
}
sub ArticleWritePlain {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Item (qw(ArticleID Email UserID)) {
if ( !$Param{$Item} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Item!"
);
return;
}
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# encode attachment if it's a postgresql backend!!!
if ( !$DBObject->GetDatabaseFunction('DirectBlob') ) {
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Param{Email} );
$Param{Email} = encode_base64( $Param{Email} );
}
# write article to db 1:1
return if !$DBObject->Do(
SQL => 'INSERT INTO article_data_mime_plain '
. ' (article_id, body, create_time, create_by, change_time, change_by) '
. ' VALUES (?, ?, current_timestamp, ?, current_timestamp, ?)',
Bind => [ \$Param{ArticleID}, \$Param{Email}, \$Param{UserID}, \$Param{UserID} ],
);
return 1;
}
sub ArticleWriteAttachment {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Item (qw(Filename ContentType ArticleID UserID)) {
if ( !IsStringWithData( $Param{$Item} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Item!"
);
return;
}
}
$Param{Filename} = $Kernel::OM->Get('Kernel::System::Main')->FilenameCleanUp(
Filename => $Param{Filename},
Type => 'Local',
NoReplace => 1,
);
my $NewFileName = $Param{Filename};
my %UsedFile;
my %Index = $Self->ArticleAttachmentIndex(
ArticleID => $Param{ArticleID},
);
for my $IndexFile ( sort keys %Index ) {
$UsedFile{ $Index{$IndexFile}->{Filename} } = 1;
}
for ( my $i = 1; $i <= 50; $i++ ) {
if ( exists $UsedFile{$NewFileName} ) {
if ( $Param{Filename} =~ /^(.*)\.(.+?)$/ ) {
$NewFileName = "$1-$i.$2";
}
else {
$NewFileName = "$Param{Filename}-$i";
}
}
}
# get file name
$Param{Filename} = $NewFileName;
# get attachment size
$Param{Filesize} = bytes::length( $Param{Content} );
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# encode attachment if it's a postgresql backend!!!
if ( !$DBObject->GetDatabaseFunction('DirectBlob') ) {
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Param{Content} );
$Param{Content} = encode_base64( $Param{Content} );
}
# set content id in angle brackets
if ( $Param{ContentID} ) {
$Param{ContentID} =~ s/^([^<].*[^>])$/<$1>/;
}
my $Disposition;
my $Filename;
if ( $Param{Disposition} ) {
( $Disposition, $Filename ) = split ';', $Param{Disposition};
}
$Disposition //= '';
# write attachment to db
return if !$DBObject->Do(
SQL => '
INSERT INTO article_data_mime_attachment (article_id, filename, content_type, content_size,
content, content_id, content_alternative, disposition, create_time, create_by,
change_time, change_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)',
Bind => [
\$Param{ArticleID}, \$Param{Filename}, \$Param{ContentType}, \$Param{Filesize},
\$Param{Content}, \$Param{ContentID}, \$Param{ContentAlternative},
\$Disposition, \$Param{UserID}, \$Param{UserID},
],
);
return 1;
}
sub ArticlePlain {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{ArticleID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need ArticleID!"
);
return;
}
# prepare/filter ArticleID
$Param{ArticleID} = quotemeta( $Param{ArticleID} );
$Param{ArticleID} =~ s/\0//g;
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# can't open article, try database
return if !$DBObject->Prepare(
SQL => 'SELECT body FROM article_data_mime_plain WHERE article_id = ?',
Bind => [ \$Param{ArticleID} ],
Encode => [0],
);
my $Data;
while ( my @Row = $DBObject->FetchrowArray() ) {
# decode attachment if it's e. g. a postgresql backend!!!
if ( !$DBObject->GetDatabaseFunction('DirectBlob') && $Row[0] !~ m/ / ) {
$Data = decode_base64( $Row[0] );
}
else {
$Data = $Row[0];
}
}
return $Data if defined $Data;
# return if we only need to check one backend
return if !$Self->{CheckAllBackends};
# return of only delete in my backend
return if $Param{OnlyMyBackend};
return $Kernel::OM->Get('Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorageFS')->ArticlePlain(
%Param,
OnlyMyBackend => 1,
);
}
sub ArticleAttachmentIndexRaw {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{ArticleID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need ArticleID!'
);
return;
}
my %Index;
my $Counter = 0;
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# try database
return if !$DBObject->Prepare(
SQL => '
SELECT filename, content_type, content_size, content_id, content_alternative,
disposition
FROM article_data_mime_attachment
WHERE article_id = ?
ORDER BY filename, id',
Bind => [ \$Param{ArticleID} ],
);
while ( my @Row = $DBObject->FetchrowArray() ) {
my $Disposition = $Row[5];
if ( !$Disposition ) {
# if no content disposition is set images with content id should be inline
if ( $Row[3] && $Row[1] =~ m{image}i ) {
$Disposition = 'inline';
}
# converted article body should be inline
elsif ( $Row[0] =~ m{file-[12]} ) {
$Disposition = 'inline';
}
# all others including attachments with content id that are not images
# should NOT be inline
else {
$Disposition = 'attachment';
}
}
# add the info the the hash
$Counter++;
$Index{$Counter} = {
Filename => $Row[0],
FilesizeRaw => $Row[2] || 0,
ContentType => $Row[1],
ContentID => $Row[3] || '',
ContentAlternative => $Row[4] || '',
Disposition => $Disposition,
};
}
# return existing index
return %Index if %Index;
# return if we only need to check one backend
return if !$Self->{CheckAllBackends};
# return if only delete in my backend
return if $Param{OnlyMyBackend};
return $Kernel::OM->Get('Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorageFS')
->ArticleAttachmentIndexRaw(
%Param,
OnlyMyBackend => 1,
);
}
sub ArticleAttachment {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Item (qw(ArticleID FileID)) {
if ( !$Param{$Item} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Item!"
);
return;
}
}
# prepare/filter ArticleID
$Param{ArticleID} = quotemeta( $Param{ArticleID} );
$Param{ArticleID} =~ s/\0//g;
# get attachment index
my %Index = $Self->ArticleAttachmentIndex(
ArticleID => $Param{ArticleID},
);
return if !$Index{ $Param{FileID} };
my %Data = %{ $Index{ $Param{FileID} } };
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# try database
return if !$DBObject->Prepare(
SQL => '
SELECT id
FROM article_data_mime_attachment
WHERE article_id = ?
ORDER BY filename, id',
Bind => [ \$Param{ArticleID} ],
Limit => $Param{FileID},
);
my $AttachmentID;
while ( my @Row = $DBObject->FetchrowArray() ) {
$AttachmentID = $Row[0];
}
return if !$DBObject->Prepare(
SQL => '
SELECT content_type, content, content_id, content_alternative, disposition, filename
FROM article_data_mime_attachment
WHERE id = ?',
Bind => [ \$AttachmentID ],
Encode => [ 1, 0, 0, 0, 1, 1 ],
);
while ( my @Row = $DBObject->FetchrowArray() ) {
$Data{ContentType} = $Row[0];
# decode attachment if it's e. g. a postgresql backend!!!
if ( !$DBObject->GetDatabaseFunction('DirectBlob') ) {
$Data{Content} = decode_base64( $Row[1] );
}
else {
$Data{Content} = $Row[1];
}
$Data{ContentID} = $Row[2] || '';
$Data{ContentAlternative} = $Row[3] || '';
$Data{Disposition} = $Row[4];
$Data{Filename} = $Row[5];
}
if ( !$Data{Disposition} ) {
# if no content disposition is set images with content id should be inline
if ( $Data{ContentID} && $Data{ContentType} =~ m{image}i ) {
$Data{Disposition} = 'inline';
}
# converted article body should be inline
elsif ( $Data{Filename} =~ m{file-[12]} ) {
$Data{Disposition} = 'inline';
}
# all others including attachments with content id that are not images
# should NOT be inline
else {
$Data{Disposition} = 'attachment';
}
}
return %Data if defined $Data{Content};
# return if we only need to check one backend
return if !$Self->{CheckAllBackends};
# return if only delete in my backend
return if $Param{OnlyMyBackend};
return $Kernel::OM->Get('Kernel::System::Ticket::Article::Backend::MIMEBase::ArticleStorageFS')->ArticleAttachment(
%Param,
OnlyMyBackend => 1,
);
}
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