# -- # 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). 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