669 lines
18 KiB
Perl
669 lines
18 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::FormDraft;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Kernel::System::VariableCheck qw(:all);
|
|
use MIME::Base64;
|
|
use Storable;
|
|
|
|
our @ObjectDependencies = (
|
|
'Kernel::System::Cache',
|
|
'Kernel::System::DB',
|
|
'Kernel::System::Log',
|
|
'Kernel::System::Storable',
|
|
);
|
|
|
|
=head1 NAME
|
|
|
|
Kernel::System::FormDraft - draft lib
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
All draft functions.
|
|
|
|
=head1 PUBLIC INTERFACE
|
|
|
|
=over 4
|
|
|
|
=cut
|
|
|
|
=item new()
|
|
|
|
create an object
|
|
|
|
use Kernel::System::ObjectManager;
|
|
local $Kernel::OM = Kernel::System::ObjectManager->new();
|
|
my $FormDraftObject = $Kernel::OM->Get('Kernel::System::FormDraft');
|
|
|
|
=cut
|
|
|
|
sub new {
|
|
my ( $Type, %Param ) = @_;
|
|
|
|
# allocate new hash for object
|
|
my $Self = {};
|
|
bless( $Self, $Type );
|
|
|
|
$Self->{CacheType} = 'FormDraft';
|
|
$Self->{CacheTTL} = 60 * 60 * 24 * 30;
|
|
|
|
return $Self;
|
|
}
|
|
|
|
=item FormDraftGet()
|
|
|
|
get draft attributes
|
|
|
|
my $FormDraft = $FormDraftObject->FormDraftGet(
|
|
FormDraftID => 123,
|
|
GetContent => 1, # optional, default 1
|
|
UserID => 123,
|
|
);
|
|
|
|
Returns (with GetContent = 0):
|
|
|
|
$FormDraft = {
|
|
FormDraftID => 123,
|
|
ObjectType => 'Ticket',
|
|
ObjectID => 12,
|
|
Action => 'AgentTicketCompose',
|
|
CreateTime => '2016-04-07 15:41:15',
|
|
CreateBy => 1,
|
|
ChangeTime => '2016-04-07 15:59:45',
|
|
ChangeBy => 2,
|
|
};
|
|
|
|
Returns (without GetContent or GetContent = 1):
|
|
|
|
$FormDraft = {
|
|
FormData => {
|
|
InformUserID => [ 123, 124, ],
|
|
Subject => 'Request for information',
|
|
...
|
|
},
|
|
FileData => [
|
|
{
|
|
'Content' => 'Dear customer\n\nthank you!',
|
|
'ContentType' => 'text/plain',
|
|
'ContentID' => undef, # optional
|
|
'Filename' => 'thankyou.txt',
|
|
'Filesize' => 25,
|
|
'FileID' => 1,
|
|
'Disposition' => 'attachment',
|
|
},
|
|
...
|
|
],
|
|
FormDraftID => 123,
|
|
ObjectType => 'Ticket',
|
|
ObjectID => 12,
|
|
Action => 'AgentTicketCompose',
|
|
CreateTime => '2016-04-07 15:41:15',
|
|
CreateBy => 1,
|
|
ChangeTime => '2016-04-07 15:59:45',
|
|
ChangeBy => 2,
|
|
Title => 'my draft',
|
|
};
|
|
|
|
=cut
|
|
|
|
sub FormDraftGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Needed (qw(FormDraftID UserID)) {
|
|
if ( !$Param{$Needed} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# determine if we should get content
|
|
$Param{GetContent} //= 1;
|
|
if ( $Param{GetContent} !~ m{ \A [01] \z }xms ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Invalid value '$Param{GetContent}' for GetContent!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check cache
|
|
my $CacheKey = 'FormDraftGet::GetContent' . $Param{GetContent} . '::ID' . $Param{FormDraftID};
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
return $Cache if $Cache;
|
|
|
|
# get database object
|
|
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
|
|
|
# prepare query
|
|
my $SQL =
|
|
'SELECT id, object_type, object_id, action, title,'
|
|
. ' create_time, create_by, change_time, change_by';
|
|
|
|
my @EncodeColumns = ( 1, 1, 1, 1, 1, 1, 1, 1, 1 );
|
|
if ( $Param{GetContent} ) {
|
|
$SQL .= ', content';
|
|
push @EncodeColumns, 0;
|
|
}
|
|
$SQL .= ' FROM form_draft WHERE id = ?';
|
|
|
|
# ask the database
|
|
return if !$DBObject->Prepare(
|
|
SQL => $SQL,
|
|
Bind => [ \$Param{FormDraftID} ],
|
|
Limit => 1,
|
|
Encode => \@EncodeColumns,
|
|
);
|
|
|
|
# fetch the result
|
|
my %FormDraft;
|
|
while ( my @Row = $DBObject->FetchrowArray() ) {
|
|
%FormDraft = (
|
|
FormDraftID => $Row[0],
|
|
ObjectType => $Row[1],
|
|
ObjectID => $Row[2],
|
|
Action => $Row[3],
|
|
Title => $Row[4] || '',
|
|
CreateTime => $Row[5],
|
|
CreateBy => $Row[6],
|
|
ChangeTime => $Row[7],
|
|
ChangeBy => $Row[8],
|
|
);
|
|
|
|
if ( $Param{GetContent} ) {
|
|
|
|
my $RawContent = $Row[9] // {};
|
|
my $StorableContent = $RawContent;
|
|
|
|
if ( !$DBObject->GetDatabaseFunction('DirectBlob') ) {
|
|
$StorableContent = MIME::Base64::decode_base64($RawContent);
|
|
}
|
|
|
|
# convert form and file data from yaml
|
|
my $Content = $Kernel::OM->Get('Kernel::System::Storable')->Deserialize( Data => $StorableContent ) // {};
|
|
|
|
$FormDraft{FormData} = $Content->{FormData};
|
|
$FormDraft{FileData} = $Content->{FileData};
|
|
}
|
|
}
|
|
|
|
# no data found
|
|
if ( !%FormDraft ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "FormDraft with ID '$Param{FormDraftID}' not found!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# always cache version without content
|
|
my $CacheKeyNoContent;
|
|
my %FormDraftNoContent;
|
|
if ( $Param{GetContent} ) {
|
|
$CacheKeyNoContent = 'FormDraftGet::GetContent0::ID' . $Param{FormDraftID};
|
|
%FormDraftNoContent = %{ Storable::dclone( \%FormDraft ) };
|
|
delete $FormDraftNoContent{FileData};
|
|
delete $FormDraftNoContent{FormData};
|
|
}
|
|
else {
|
|
$CacheKeyNoContent = $CacheKey;
|
|
%FormDraftNoContent = %FormDraft;
|
|
}
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKeyNoContent,
|
|
Value => \%FormDraftNoContent,
|
|
);
|
|
|
|
return \%FormDraft if !$Param{GetContent};
|
|
|
|
# set cache with content (shorter cache time due to potentially large content)
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
TTL => 60 * 60,
|
|
Key => $CacheKey,
|
|
Value => \%FormDraft,
|
|
);
|
|
|
|
return \%FormDraft;
|
|
}
|
|
|
|
=item FormDraftAdd()
|
|
|
|
add a new draft
|
|
|
|
my $Success = $FormDraftObject->FormDraftAdd(
|
|
FormData => {
|
|
InformUserID => [ 123, 124, ],
|
|
Subject => 'Request for information',
|
|
...
|
|
},
|
|
FileData => [ # optional
|
|
{
|
|
'Content' => 'Dear customer\n\nthank you!',
|
|
'ContentType' => 'text/plain',
|
|
'ContentID' => undef, # optional
|
|
'Filename' => 'thankyou.txt',
|
|
'Filesize' => 25,
|
|
'FileID' => 1,
|
|
'Disposition' => 'attachment',
|
|
},
|
|
...
|
|
],
|
|
ObjectType => 'Ticket',
|
|
ObjectID => 12,
|
|
Action => 'AgentTicketCompose',
|
|
Title => 'my draft', # optional
|
|
UserID => 123,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub FormDraftAdd {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Needed (qw(FormData ObjectType Action)) {
|
|
if ( !$Param{$Needed} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
for my $Needed (qw(ObjectID UserID)) {
|
|
if ( !IsInteger( $Param{$Needed} ) ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
|
|
|
# serialize form and file data
|
|
my $StorableContent = $Kernel::OM->Get('Kernel::System::Storable')->Serialize(
|
|
Data => {
|
|
FormData => $Param{FormData},
|
|
FileData => $Param{FileData} || [],
|
|
},
|
|
);
|
|
|
|
my $Content = $StorableContent;
|
|
if ( !$DBObject->GetDatabaseFunction('DirectBlob') ) {
|
|
$Content = MIME::Base64::encode_base64($StorableContent);
|
|
}
|
|
|
|
# add to database
|
|
return if !$DBObject->Do(
|
|
SQL =>
|
|
'INSERT INTO form_draft'
|
|
. ' (object_type, object_id, action, title, content, create_time, create_by, change_time, change_by)'
|
|
. ' VALUES (?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)',
|
|
Bind => [
|
|
\$Param{ObjectType}, \$Param{ObjectID}, \$Param{Action}, \$Param{Title}, \$Content,
|
|
\$Param{UserID}, \$Param{UserID},
|
|
],
|
|
);
|
|
|
|
# delete affected caches
|
|
$Self->_DeleteAffectedCaches(%Param);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=item FormDraftUpdate()
|
|
|
|
update an existing draft
|
|
|
|
my $Success = $FormDraftObject->FormDraftUpdate(
|
|
FormData => {
|
|
InformUserID => [ 123, 124, ],
|
|
Subject => 'Request for information',
|
|
...
|
|
},
|
|
FileData => [ # optional
|
|
{
|
|
'Content' => 'Dear customer\n\nthank you!',
|
|
'ContentType' => 'text/plain',
|
|
'ContentID' => undef, # optional
|
|
'Filename' => 'thankyou.txt',
|
|
'Filesize' => 25,
|
|
'FileID' => 1,
|
|
'Disposition' => 'attachment',
|
|
},
|
|
...
|
|
],
|
|
ObjectType => 'Ticket',
|
|
ObjectID => 12,
|
|
Action => 'AgentTicketCompose',
|
|
Title => 'my draft',
|
|
FormDraftID => 1,
|
|
UserID => 123,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub FormDraftUpdate {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Needed (qw(FormData ObjectType Action)) {
|
|
if ( !$Param{$Needed} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
for my $Needed (qw(ObjectID FormDraftID UserID)) {
|
|
if ( !IsInteger( $Param{$Needed} ) ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check if specified draft already exists and do sanity checks
|
|
my $FormDraft = $Self->FormDraftGet(
|
|
FormDraftID => $Param{FormDraftID},
|
|
GetContent => 0,
|
|
UserID => $Param{UserID},
|
|
);
|
|
if ( !$FormDraft ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "FormDraft with ID '$Param{FormDraftID}' not found!",
|
|
);
|
|
return;
|
|
}
|
|
VALIDATEPARAM:
|
|
for my $ValidateParam (qw(ObjectType ObjectID Action)) {
|
|
next VALIDATEPARAM if $Param{$ValidateParam} eq $FormDraft->{$ValidateParam};
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message =>
|
|
"Param '$ValidateParam' for draft with ID '$Param{FormDraftID}'"
|
|
. " must not be changed on update!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
|
|
|
# serialize form and file data
|
|
my $StorableContent = $Kernel::OM->Get('Kernel::System::Storable')->Serialize(
|
|
Data => {
|
|
FormData => $Param{FormData},
|
|
FileData => $Param{FileData} || [],
|
|
},
|
|
);
|
|
|
|
my $Content = $StorableContent;
|
|
if ( !$DBObject->GetDatabaseFunction('DirectBlob') ) {
|
|
$Content = MIME::Base64::encode_base64($StorableContent);
|
|
}
|
|
|
|
# add to database
|
|
return if !$DBObject->Do(
|
|
SQL =>
|
|
'UPDATE form_draft'
|
|
. ' SET title = ?, content = ?, change_time = current_timestamp, change_by = ?'
|
|
. ' WHERE id = ?',
|
|
Bind => [ \$Param{Title}, \$Content, \$Param{UserID}, \$Param{FormDraftID}, ],
|
|
);
|
|
|
|
# delete affected caches
|
|
$Self->_DeleteAffectedCaches(%Param);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=item FormDraftDelete()
|
|
|
|
remove draft
|
|
|
|
my $Success = $FormDraftObject->FormDraftDelete(
|
|
FormDraftID => 123,
|
|
UserID => 123,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub FormDraftDelete {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Needed (qw(FormDraftID UserID)) {
|
|
if ( !$Param{$Needed} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# get draft data as sanity check and to determine which caches should be removed
|
|
# use database query directly (we don't need raw content)
|
|
my $FormDraft = $Self->FormDraftGet(
|
|
FormDraftID => $Param{FormDraftID},
|
|
GetContent => 0,
|
|
UserID => $Param{UserID},
|
|
);
|
|
if ( !$FormDraft ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "FormDraft with ID '$Param{FormDraftID}' not found!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# remove from database
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'DELETE FROM form_draft WHERE id = ?',
|
|
Bind => [ \$Param{FormDraftID} ],
|
|
);
|
|
|
|
# delete affected caches
|
|
$Self->_DeleteAffectedCaches( %{$FormDraft} );
|
|
|
|
return 1;
|
|
}
|
|
|
|
=item FormDraftListGet()
|
|
|
|
get list of drafts, optionally filtered by object type, object id and action
|
|
|
|
my $FormDraftList = $FormDraftObject->FormDraftListGet(
|
|
ObjectType => 'Ticket', # optional
|
|
ObjectID => 123, # optional
|
|
Action => 'AgentTicketCompose', # optional
|
|
UserID => 123,
|
|
);
|
|
|
|
Returns:
|
|
|
|
$FormDraftList = [
|
|
{
|
|
FormDraftID => 123,
|
|
ObjectType => 'Ticket',
|
|
ObjectID => 12,
|
|
Action => 'AgentTicketCompose',
|
|
Title => 'my draft',
|
|
CreateTime => '2016-04-07 15:41:15',
|
|
CreateBy => 1,
|
|
ChangeTime => '2016-04-07 15:59:45',
|
|
ChangeBy => 2,
|
|
},
|
|
...
|
|
];
|
|
|
|
=cut
|
|
|
|
sub FormDraftListGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need UserID!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check cache
|
|
my $CacheKey = 'FormDraftListGet';
|
|
RESTRICTION:
|
|
for my $Restriction (qw(ObjectType Action ObjectID)) {
|
|
next RESTRICTION if !IsStringWithData( $Param{$Restriction} );
|
|
$CacheKey .= '::' . $Restriction . $Param{$Restriction};
|
|
}
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
return $Cache if $Cache;
|
|
|
|
# prepare database restrictions by given parameters
|
|
my %ParamToField = (
|
|
ObjectType => 'object_type',
|
|
Action => 'action',
|
|
ObjectID => 'object_id',
|
|
);
|
|
my $SQLExt = '';
|
|
my @Bind;
|
|
RESTRICTION:
|
|
for my $Restriction (qw(ObjectType Action ObjectID)) {
|
|
next RESTRICTION if !IsStringWithData( $Param{$Restriction} );
|
|
$SQLExt .= $SQLExt ? ' AND ' : ' WHERE ';
|
|
$SQLExt .= $ParamToField{$Restriction} . ' = ?';
|
|
push @Bind, \$Param{$Restriction};
|
|
}
|
|
|
|
# get database object
|
|
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
|
|
|
# ask the database
|
|
return if !$DBObject->Prepare(
|
|
SQL =>
|
|
'SELECT id, object_type, object_id, action, title,'
|
|
. ' create_time, create_by, change_time, change_by'
|
|
. ' FROM form_draft' . $SQLExt
|
|
. ' ORDER BY id ASC',
|
|
Bind => \@Bind,
|
|
);
|
|
|
|
# fetch the results
|
|
my @FormDrafts;
|
|
while ( my @Row = $DBObject->FetchrowArray() ) {
|
|
push @FormDrafts, {
|
|
FormDraftID => $Row[0],
|
|
ObjectType => $Row[1],
|
|
ObjectID => $Row[2],
|
|
Action => $Row[3],
|
|
Title => $Row[4] || '',
|
|
CreateTime => $Row[5],
|
|
CreateBy => $Row[6],
|
|
ChangeTime => $Row[7],
|
|
ChangeBy => $Row[8],
|
|
};
|
|
}
|
|
|
|
# set cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => \@FormDrafts,
|
|
);
|
|
|
|
return \@FormDrafts;
|
|
}
|
|
|
|
=item _DeleteAffectedCaches()
|
|
|
|
remove all potentially affected caches
|
|
|
|
my $Success = $FormDraftObject->_DeleteAffectedCaches(
|
|
FormDraftID => 1, # optional
|
|
ObjectType => 'Ticket',
|
|
ObjectID => 12,
|
|
Action => 'AgentTicketCompose',
|
|
);
|
|
|
|
=cut
|
|
|
|
sub _DeleteAffectedCaches {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# prepare affected cache keys
|
|
my @CacheKeys = (
|
|
'FormDraftListGet',
|
|
'FormDraftListGet::ObjectType' . $Param{ObjectType},
|
|
'FormDraftListGet::Action' . $Param{Action},
|
|
'FormDraftListGet::ObjectID' . $Param{ObjectID},
|
|
'FormDraftListGet::ObjectType' . $Param{ObjectType}
|
|
. '::Action' . $Param{Action},
|
|
'FormDraftListGet::ObjectType' . $Param{ObjectType}
|
|
. '::ObjectID' . $Param{ObjectID},
|
|
'FormDraftListGet::Action' . $Param{Action}
|
|
. '::ObjectID' . $Param{ObjectID},
|
|
'FormDraftListGet::ObjectType' . $Param{ObjectType}
|
|
. '::Action' . $Param{Action}
|
|
. '::ObjectID' . $Param{ObjectID},
|
|
);
|
|
if ( $Param{FormDraftID} ) {
|
|
push @CacheKeys,
|
|
'FormDraftGet::GetContent0::ID' . $Param{FormDraftID},
|
|
'FormDraftGet::GetContent1::ID' . $Param{FormDraftID};
|
|
}
|
|
|
|
# delete affected caches
|
|
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
|
|
for my $CacheKey (@CacheKeys) {
|
|
$CacheObject->Delete(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
1;
|
|
|
|
=back
|
|
|
|
=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
|