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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,171 @@
# --
# 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::Web::InterfaceInstaller;
use strict;
use warnings;
use Kernel::Language qw(Translatable);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::Output::HTML::Layout',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::Web::Request',
);
=head1 NAME
Kernel::System::Web::InterfaceInstaller - the installer web interface
=head1 DESCRIPTION
the global installer web interface
=head1 PUBLIC INTERFACE
=head2 new()
create installer web interface object
use Kernel::System::Web::InterfaceInstaller;
my $Debug = 0;
my $Interface = Kernel::System::Web::InterfaceInstaller->new( Debug => $Debug );
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# get debug level
$Self->{Debug} = $Param{Debug} || 0;
$Kernel::OM->ObjectParamAdd(
'Kernel::System::Log' => {
LogPrefix => $Kernel::OM->Get('Kernel::Config')->Get('CGILogPrefix') || 'Installer',
},
'Kernel::Output::HTML::Layout' => {
InstallerOnly => 1,
},
'Kernel::System::Web::Request' => {
WebRequest => $Param{WebRequest} || 0,
},
);
# debug info
if ( $Self->{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => 'Global handle started...',
);
}
return $Self;
}
=head2 Run()
execute the object
$Interface->Run();
=cut
sub Run {
my $Self = shift;
# get common framework params
my %Param;
my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
$Param{Action} = $ParamObject->GetParam( Param => 'Action' ) || 'Installer';
$Param{Subaction} = $ParamObject->GetParam( Param => 'Subaction' ) || '';
$Param{NextScreen} = $ParamObject->GetParam( Param => 'NextScreen' ) || '';
$Kernel::OM->ObjectParamAdd(
'Kernel::Output::HTML::Layout' => {
%Param,
},
);
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
# check secure mode
if ( $Kernel::OM->Get('Kernel::Config')->Get('SecureMode') ) {
print $LayoutObject->Header();
print $LayoutObject->Error(
Message => Translatable('SecureMode active!'),
Comment => Translatable(
'If you want to re-run the Installer, disable the SecureMode in the SysConfig.'
),
);
print $LayoutObject->Footer();
}
# run modules if a version value exists
elsif ( $Kernel::OM->Get('Kernel::System::Main')->Require("Kernel::Modules::$Param{Action}") ) {
# proof of concept! - create $GenericObject
my $GenericObject = ( 'Kernel::Modules::' . $Param{Action} )->new(
%Param,
Debug => $Self->{Debug},
);
print $GenericObject->Run();
}
# else print an error screen
else {
# create new LayoutObject with '%Param'
print $LayoutObject->Header();
print $LayoutObject->Error(
Message => $LayoutObject->{LanguageObject}->Translate( 'Action "%s" not found!', $Param{Action} ),
Comment => Translatable('Please contact the administrator.'),
);
print $LayoutObject->Footer();
}
return;
}
sub DESTROY {
my $Self = shift;
# debug info
if ( $Self->{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => 'Global handle stopped.',
);
}
return 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

View File

@@ -0,0 +1,297 @@
# --
# 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::Web::InterfacePublic;
use strict;
use warnings;
use Kernel::Language qw(Translatable);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::Output::HTML::Layout',
'Kernel::System::DB',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::Web::Request',
);
=head1 NAME
Kernel::System::Web::InterfacePublic - the public web interface
=head1 DESCRIPTION
the global public web interface
=head1 PUBLIC INTERFACE
=head2 new()
create public web interface object
use Kernel::System::Web::InterfacePublic;
my $Debug = 0;
my $Interface = Kernel::System::Web::InterfacePublic->new(
Debug => $Debug,
WebRequest => CGI::Fast->new(), # optional, e. g. if fast cgi is used, the CGI object is already provided
);
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# get debug level
$Self->{Debug} = $Param{Debug} || 0;
# performance log
$Self->{PerformanceLogStart} = time();
$Kernel::OM->ObjectParamAdd(
'Kernel::System::Log' => {
LogPrefix => $Kernel::OM->Get('Kernel::Config')->Get('CGILogPrefix'),
},
'Kernel::System::Web::Request' => {
WebRequest => $Param{WebRequest} || 0,
},
);
# debug info
if ( $Self->{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => 'Global handle started...',
);
}
return $Self;
}
=head2 Run()
execute the object
$Interface->Run();
=cut
sub Run {
my $Self = shift;
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $QueryString = $ENV{QUERY_STRING} || '';
# Check if https forcing is active, and redirect if needed.
if ( $ConfigObject->Get('HTTPSForceRedirect') ) {
# Some web servers do not set HTTPS environment variable, so it's not possible to easily know if we are using
# https protocol. Look also for similarly named keys in environment hash, since this should prevent loops in
# certain cases.
if (
(
!defined $ENV{HTTPS}
&& !grep {/^HTTPS(?:_|$)/} keys %ENV
)
|| $ENV{HTTPS} ne 'on'
)
{
my $Host = $ENV{HTTP_HOST} || $ConfigObject->Get('FQDN');
# Redirect with 301 code. Add two new lines at the end, so HTTP headers are validated correctly.
print "Status: 301 Moved Permanently\nLocation: https://$Host$ENV{REQUEST_URI}\n\n";
return;
}
}
my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
my %Param;
# get session id
$Param{SessionName} = $ConfigObject->Get('CustomerPanelSessionName') || 'CSID';
$Param{SessionID} = $ParamObject->GetParam( Param => $Param{SessionName} ) || '';
# drop old session id (if exists)
$QueryString =~ s/(\?|&|;|)$Param{SessionName}(=&|=;|=.+?&|=.+?$)/;/g;
# define framework params
my $FrameworkParams = {
Lang => '',
Action => '',
Subaction => '',
RequestedURL => $QueryString,
};
for my $Key ( sort keys %{$FrameworkParams} ) {
$Param{$Key} = $ParamObject->GetParam( Param => $Key )
|| $FrameworkParams->{$Key};
}
# validate language
if ( $Param{Lang} && $Param{Lang} !~ m{\A[a-z]{2}(?:_[A-Z]{2})?\z}xms ) {
delete $Param{Lang};
}
# Check if the browser sends the SessionID cookie and set the SessionID-cookie
# as SessionID! GET or POST SessionID have the lowest priority.
if ( $ConfigObject->Get('SessionUseCookie') ) {
$Param{SessionIDCookie} = $ParamObject->GetCookie( Key => $Param{SessionName} );
if ( $Param{SessionIDCookie} ) {
$Param{SessionID} = $Param{SessionIDCookie};
}
}
# get common application and add-on application params
# Important!
# This must be done before creating the layout object,
# because otherwise the action parameter is not passed and then
# the loader can not load module specific JavaScript and CSS
# For details see bug: http://bugs.otrs.org/show_bug.cgi?id=6471
my %CommonObjectParam = %{ $ConfigObject->Get('PublicFrontend::CommonParam') };
for my $Key ( sort keys %CommonObjectParam ) {
$Param{$Key} = $ParamObject->GetParam( Param => $Key ) || $CommonObjectParam{$Key};
}
# security check Action Param (replace non-word chars)
$Param{Action} =~ s/\W//g;
$Kernel::OM->ObjectParamAdd(
'Kernel::Output::HTML::Layout' => {
%Param,
SessionIDCookie => 1,
Debug => $Self->{Debug},
},
);
my $DBCanConnect = $Kernel::OM->Get('Kernel::System::DB')->Connect();
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
if ( !$DBCanConnect ) {
$LayoutObject->CustomerFatalError(
Comment => Translatable('Please contact the administrator.'),
);
}
if ( $ParamObject->Error() ) {
$LayoutObject->CustomerFatalError(
Message => $ParamObject->Error(),
Comment => Translatable('Please contact the administrator.'),
);
}
# run modules if a version value exists
if ( !$Kernel::OM->Get('Kernel::System::Main')->Require("Kernel::Modules::$Param{Action}") ) {
$LayoutObject->CustomerFatalError(
Comment => Translatable('Please contact the administrator.'),
);
return 1;
}
# module registry
my $ModuleReg = $ConfigObject->Get('PublicFrontend::Module')->{ $Param{Action} };
if ( !$ModuleReg ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"Module Kernel::Modules::$Param{Action} not registered in Kernel/Config.pm!",
);
$LayoutObject->CustomerFatalError(
Comment => Translatable('Please contact the administrator.'),
);
return;
}
# debug info
if ( $Self->{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => 'Kernel::Modules::' . $Param{Action} . '->new',
);
}
my $FrontendObject = ( 'Kernel::Modules::' . $Param{Action} )->new(
UserID => 1,
%Param,
Debug => $Self->{Debug},
);
# debug info
if ( $Self->{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => 'Kernel::Modules::' . $Param{Action} . '->run',
);
}
# ->Run $Action with $FrontendObject
$LayoutObject->Print( Output => \$FrontendObject->Run() );
# log request time
if ( $ConfigObject->Get('PerformanceLog') ) {
if ( ( !$QueryString && $Param{Action} ) || $QueryString !~ /Action=/ ) {
$QueryString = 'Action=' . $Param{Action} . '&Subaction=' . $Param{Subaction};
}
my $File = $ConfigObject->Get('PerformanceLog::File');
## no critic
if ( open my $Out, '>>', $File ) {
## use critic
print $Out time()
. '::Public::'
. ( time() - $Self->{PerformanceLogStart} )
. "::-::$QueryString\n";
close $Out;
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => 'Response::Public: '
. ( time() - $Self->{PerformanceLogStart} )
. "s taken (URL:$QueryString)",
);
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Can't write $File: $!",
);
}
}
return 1;
}
sub DESTROY {
my $Self = shift;
# debug info
if ( $Self->{Debug} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => 'Global handle stopped.',
);
}
return 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

View File

@@ -0,0 +1,570 @@
# --
# 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::Web::Request;
use strict;
use warnings;
use CGI ();
use CGI::Carp;
use File::Path qw();
use Kernel::System::VariableCheck qw(:all);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::CheckItem',
'Kernel::System::Encode',
'Kernel::System::Web::UploadCache',
'Kernel::System::FormDraft',
'Kernel::System::Main',
);
=head1 NAME
Kernel::System::Web::Request - global CGI interface
=head1 DESCRIPTION
All cgi param functions.
=head1 PUBLIC INTERFACE
=head2 new()
create param object. Do not use it directly, instead use:
use Kernel::System::ObjectManager;
local $Kernel::OM = Kernel::System::ObjectManager->new(
'Kernel::System::Web::Request' => {
WebRequest => CGI::Fast->new(), # optional, e. g. if fast cgi is used
}
);
my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
If Kernel::System::Web::Request is instantiated several times, they will share the
same CGI data (this can be helpful in filters which do not have access to the
ParamObject, for example.
If you need to reset the CGI data before creating a new instance, use
CGI::initialize_globals();
before calling Kernel::System::Web::Request->new();
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# get config object
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
# max 5 MB posts
$CGI::POST_MAX = $ConfigObject->Get('WebMaxFileUpload') || 1024 * 1024 * 5; ## no critic
# query object (in case use already existing WebRequest, e. g. fast cgi)
$Self->{Query} = $Param{WebRequest} || CGI->new();
return $Self;
}
=head2 Error()
to get the error back
if ( $ParamObject->Error() ) {
print STDERR $ParamObject->Error() . "\n";
}
=cut
sub Error {
my ( $Self, %Param ) = @_;
# Workaround, do not check cgi_error() with perlex, CGI module is not
# working with perlex.
if ( $ENV{'GATEWAY_INTERFACE'} && $ENV{'GATEWAY_INTERFACE'} =~ /^CGI-PerlEx/ ) {
return;
}
return if !$Self->{Query}->cgi_error();
## no critic
return $Self->{Query}->cgi_error() . ' - POST_MAX=' . ( $CGI::POST_MAX / 1024 ) . 'KB';
## use critic
}
=head2 GetParam()
to get single request parameters. By default, trimming is performed on the data.
my $Param = $ParamObject->GetParam(
Param => 'ID',
Raw => 1, # optional, input data is not changed
);
=cut
sub GetParam {
my ( $Self, %Param ) = @_;
my $Value = $Self->{Query}->param( $Param{Param} );
# Fallback to query string for mixed requests.
my $RequestMethod = $Self->{Query}->request_method() // '';
if ( $RequestMethod eq 'POST' && !defined $Value ) {
$Value = $Self->{Query}->url_param( $Param{Param} );
}
$Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$Value );
my $Raw = defined $Param{Raw} ? $Param{Raw} : 0;
if ( !$Raw ) {
# If it is a plain string, perform trimming
if ( ref \$Value eq 'SCALAR' ) {
$Kernel::OM->Get('Kernel::System::CheckItem')->StringClean(
StringRef => \$Value,
TrimLeft => 1,
TrimRight => 1,
);
}
}
return $Value;
}
=head2 GetParamNames()
to get names of all parameters passed to the script.
my @ParamNames = $ParamObject->GetParamNames();
Example:
Called URL: index.pl?Action=AdminSystemConfiguration;Subaction=Save;Name=Config::Option::Valid
my @ParamNames = $ParamObject->GetParamNames();
print join " :: ", @ParamNames;
#prints Action :: Subaction :: Name
=cut
sub GetParamNames {
my $Self = shift;
# fetch all names
my @ParamNames = $Self->{Query}->param();
# Fallback to query string for mixed requests.
my $RequestMethod = $Self->{Query}->request_method() // '';
if ( $RequestMethod eq 'POST' ) {
my %POSTNames;
@POSTNames{@ParamNames} = @ParamNames;
my @GetNames = $Self->{Query}->url_param();
GETNAME:
for my $GetName (@GetNames) {
next GETNAME if !defined $GetName;
push @ParamNames, $GetName if !exists $POSTNames{$GetName};
}
}
for my $Name (@ParamNames) {
$Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$Name );
}
return @ParamNames;
}
=head2 GetArray()
to get array request parameters.
By default, trimming is performed on the data.
my @Param = $ParamObject->GetArray(
Param => 'ID',
Raw => 1, # optional, input data is not changed
);
=cut
sub GetArray {
my ( $Self, %Param ) = @_;
my @Values = $Self->{Query}->multi_param( $Param{Param} );
# Fallback to query string for mixed requests.
my $RequestMethod = $Self->{Query}->request_method() // '';
if ( $RequestMethod eq 'POST' && !@Values ) {
@Values = $Self->{Query}->url_param( $Param{Param} );
}
$Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \@Values );
my $Raw = defined $Param{Raw} ? $Param{Raw} : 0;
if ( !$Raw ) {
# get check item object
my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem');
VALUE:
for my $Value (@Values) {
# don't validate CGI::File::Temp objects from file uploads
next VALUE if !$Value || ref \$Value ne 'SCALAR';
$CheckItemObject->StringClean(
StringRef => \$Value,
TrimLeft => 1,
TrimRight => 1,
);
}
}
return @Values;
}
=head2 GetUploadAll()
gets file upload data.
my %File = $ParamObject->GetUploadAll(
Param => 'FileParam', # the name of the request parameter containing the file data
);
returns (
Filename => 'abc.txt',
ContentType => 'text/plain',
Content => 'Some text',
);
=cut
sub GetUploadAll {
my ( $Self, %Param ) = @_;
# get upload
my $Upload = $Self->{Query}->upload( $Param{Param} );
return if !$Upload;
# get real file name
my $UploadFilenameOrig = $Self->GetParam( Param => $Param{Param} ) || 'unknown';
my $NewFileName = "$UploadFilenameOrig"; # use "" to get filename of anony. object
$Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$NewFileName );
# replace all devices like c: or d: and dirs for IE!
$NewFileName =~ s/.:\\(.*)/$1/g;
$NewFileName =~ s/.*\\(.+?)/$1/g;
# return a string
my $Content = '';
while (<$Upload>) {
$Content .= $_;
}
close $Upload;
my $ContentType = $Self->_GetUploadInfo(
Filename => $UploadFilenameOrig,
Header => 'Content-Type',
);
return (
Filename => $NewFileName,
Content => $Content,
ContentType => $ContentType,
);
}
sub _GetUploadInfo {
my ( $Self, %Param ) = @_;
# get file upload info
my $FileInfo = $Self->{Query}->uploadInfo( $Param{Filename} );
# return if no upload info exists
return 'application/octet-stream' if !$FileInfo;
# return if no content type of upload info exists
return 'application/octet-stream' if !$FileInfo->{ $Param{Header} };
# return content type of upload info
return $FileInfo->{ $Param{Header} };
}
=head2 SetCookie()
set a cookie
$ParamObject->SetCookie(
Key => ID,
Value => 123456,
Expires => '+3660s',
Path => 'otrs/', # optional, only allow cookie for given path
Secure => 1, # optional, set secure attribute to disable cookie on HTTP (HTTPS only)
HTTPOnly => 1, # optional, sets HttpOnly attribute of cookie to prevent access via JavaScript
);
=cut
sub SetCookie {
my ( $Self, %Param ) = @_;
$Param{Path} ||= '';
return $Self->{Query}->cookie(
-name => $Param{Key},
-value => $Param{Value},
-expires => $Param{Expires},
-secure => $Param{Secure} || '',
-httponly => $Param{HTTPOnly} || '',
-path => '/' . $Param{Path},
);
}
=head2 GetCookie()
get a cookie
my $String = $ParamObject->GetCookie(
Key => ID,
);
=cut
sub GetCookie {
my ( $Self, %Param ) = @_;
return $Self->{Query}->cookie( $Param{Key} );
}
=head2 IsAJAXRequest()
checks if the current request was sent by AJAX
my $IsAJAXRequest = $ParamObject->IsAJAXRequest();
=cut
sub IsAJAXRequest {
my ( $Self, %Param ) = @_;
return ( $Self->{Query}->http('X-Requested-With') // '' ) eq 'XMLHttpRequest' ? 1 : 0;
}
=head2 LoadFormDraft()
Load specified draft.
This will read stored draft data and inject it into the param object
for transparent use by frontend module.
my $FormDraftID = $ParamObject->LoadFormDraft(
FormDraftID => 123,
UserID => 1,
);
=cut
sub LoadFormDraft {
my ( $Self, %Param ) = @_;
return if !$Param{FormDraftID} || !$Param{UserID};
# get draft data
my $FormDraft = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftGet(
FormDraftID => $Param{FormDraftID},
UserID => $Param{UserID},
);
return if !IsHashRefWithData($FormDraft);
# Verify action.
my $Action = $Self->GetParam( Param => 'Action' );
return if $FormDraft->{Action} ne $Action;
# add draft name to form data
$FormDraft->{FormData}->{FormDraftTitle} = $FormDraft->{Title};
# create FormID and add to form data
my $FormID = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDCreate();
$FormDraft->{FormData}->{FormID} = $FormID;
# set form data to param object, depending on type
KEY:
for my $Key ( sort keys %{ $FormDraft->{FormData} } ) {
my $Value = $FormDraft->{FormData}->{$Key} // '';
# array value
if ( IsArrayRefWithData($Value) ) {
$Self->{Query}->param(
-name => $Key,
-values => $Value,
);
next KEY;
}
# scalar value
$Self->{Query}->param(
-name => $Key,
-value => $Value,
);
}
# add UploadCache data
my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache');
for my $File ( @{ $FormDraft->{FileData} } ) {
return if !$UploadCacheObject->FormIDAddFile(
%{$File},
FormID => $FormID,
);
}
return $Param{FormDraftID};
}
=head2 SaveFormDraft()
Create or replace draft using data from param object and upload cache.
Specified params can be overwritten if necessary.
my $FormDraftID = $ParamObject->SaveFormDraft(
UserID => 1
ObjectType => 'Ticket',
ObjectID => 123,
OverrideParams => { # optional, can contain strings and array references
Subaction => undef,
UserID => 1,
CustomParam => [ 1, 2, 3, ],
...
},
);
=cut
sub SaveFormDraft {
my ( $Self, %Param ) = @_;
# check params
return if !$Param{UserID} || !$Param{ObjectType} || !IsInteger( $Param{ObjectID} );
# gather necessary data for backend
my %MetaParams;
for my $Param (qw(Action FormDraftID FormDraftTitle FormID)) {
$MetaParams{$Param} = $Self->GetParam(
Param => $Param,
);
}
return if !$MetaParams{Action};
# determine session name param (SessionUseCookie = 0) for exclusion
my $SessionName = $Kernel::OM->Get('Kernel::Config')->Get('SessionName') || 'SessionID';
# compile override list
my %OverrideParams = IsHashRefWithData( $Param{OverrideParams} ) ? %{ $Param{OverrideParams} } : ();
# these params must always be excluded for safety, they take precedence
for my $Name (
qw(Action ChallengeToken FormID FormDraftID FormDraftTitle FormDraftAction LoadFormDraftID),
$SessionName
)
{
$OverrideParams{$Name} = undef;
}
# Gather all params.
# Exclude, add or override by using OverrideParams if necessary.
my @ParamNames = $Self->GetParamNames();
my %ParamSeen;
my %FormData;
PARAM:
for my $Param ( @ParamNames, sort keys %OverrideParams ) {
next PARAM if $ParamSeen{$Param}++;
my $Value;
# check for overrides first
if ( exists $OverrideParams{$Param} ) {
# allow only strings and array references as value
if (
IsStringWithData( $OverrideParams{$Param} )
|| IsArrayRefWithData( $OverrideParams{$Param} )
)
{
$Value = $OverrideParams{$Param};
}
# skip all other parameters (including those specified to be excluded by using 'undef')
else {
next PARAM;
}
}
# get other values from param object
if ( !defined $Value ) {
my @Values = $Self->GetArray( Param => $Param );
next PARAM if !IsArrayRefWithData( \@Values );
# store single occurances as string
if ( scalar @Values == 1 ) {
$Value = $Values[0];
}
# store multiple occurances as array reference
else {
$Value = \@Values;
}
}
$FormData{$Param} = $Value;
}
# get files from upload cache
my @FileData = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDGetAllFilesData(
FormID => $MetaParams{FormID},
);
# prepare data to add or update draft
my %FormDraft = (
FormData => \%FormData,
FileData => \@FileData,
FormDraftID => $MetaParams{FormDraftID},
ObjectType => $Param{ObjectType},
ObjectID => $Param{ObjectID},
Action => $MetaParams{Action},
Title => $MetaParams{FormDraftTitle},
UserID => $Param{UserID},
);
# update draft
if ( $MetaParams{FormDraftID} ) {
return if !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftUpdate(%FormDraft);
return 1;
}
# create new draft
return if !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftAdd(%FormDraft);
return 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

View File

@@ -0,0 +1,193 @@
# --
# 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::Web::UploadCache;
use strict;
use warnings;
our @ObjectDependencies = (
'Kernel::Config',
);
=head1 NAME
Kernel::System::Web::UploadCache - an upload file system cache
=head1 DESCRIPTION
All upload cache functions.
=head1 PUBLIC INTERFACE
=head2 new()
Don't use the constructor directly, use the ObjectManager instead:
my $WebUploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache');
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
my $GenericModule = $Kernel::OM->Get('Kernel::Config')->Get('WebUploadCacheModule')
|| 'Kernel::System::Web::UploadCache::DB';
# load generator auth module
$Self->{Backend} = $Kernel::OM->Get($GenericModule);
return $Self if $Self->{Backend};
return;
}
=head2 FormIDCreate()
create a new Form ID
my $FormID = $UploadCacheObject->FormIDCreate();
=cut
sub FormIDCreate {
my ( $Self, %Param ) = @_;
return $Self->{Backend}->FormIDCreate(%Param);
}
=head2 FormIDRemove()
remove all data for a provided Form ID
$UploadCacheObject->FormIDRemove( FormID => 123456 );
=cut
sub FormIDRemove {
my ( $Self, %Param ) = @_;
return $Self->{Backend}->FormIDRemove(%Param);
}
=head2 FormIDAddFile()
add a file to a Form ID
$UploadCacheObject->FormIDAddFile(
FormID => 12345,
Filename => 'somefile.html',
Content => $FileInString,
ContentType => 'text/html',
Disposition => 'inline', # optional
);
ContentID is optional (automatically generated if not given on disposition = inline)
$UploadCacheObject->FormIDAddFile(
FormID => 12345,
Filename => 'somefile.html',
Content => $FileInString,
ContentID => 'some_id@example.com',
ContentType => 'text/html',
Disposition => 'inline', # optional
);
=cut
sub FormIDAddFile {
my ( $Self, %Param ) = @_;
return $Self->{Backend}->FormIDAddFile(%Param);
}
=head2 FormIDRemoveFile()
removes a file from a form id
$UploadCacheObject->FormIDRemoveFile(
FormID => 12345,
FileID => 1,
);
=cut
sub FormIDRemoveFile {
my ( $Self, %Param ) = @_;
return $Self->{Backend}->FormIDRemoveFile(%Param);
}
=head2 FormIDGetAllFilesData()
returns an array with a hash ref of all files for a Form ID
my @Data = $UploadCacheObject->FormIDGetAllFilesData(
FormID => 12345,
);
Return data of on hash is Content, ContentType, ContentID, Filename, Filesize, FileID;
=cut
sub FormIDGetAllFilesData {
my ( $Self, %Param ) = @_;
return @{ $Self->{Backend}->FormIDGetAllFilesData(%Param) };
}
=head2 FormIDGetAllFilesMeta()
returns an array with a hash ref of all files for a Form ID
Note: returns no content, only meta data.
my @Data = $UploadCacheObject->FormIDGetAllFilesMeta(
FormID => 12345,
);
Return data of hash is ContentType, ContentID, Filename, Filesize, FileID;
=cut
sub FormIDGetAllFilesMeta {
my ( $Self, %Param ) = @_;
return @{ $Self->{Backend}->FormIDGetAllFilesMeta(%Param) };
}
=head2 FormIDCleanUp()
Removed no longer needed temporary files.
Each file older than 1 day will be removed.
$UploadCacheObject->FormIDCleanUp();
=cut
sub FormIDCleanUp {
my ( $Self, %Param ) = @_;
return $Self->{Backend}->FormIDCleanUp(%Param);
}
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

View File

@@ -0,0 +1,268 @@
# --
# 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::Web::UploadCache::DB;
use strict;
use warnings;
use MIME::Base64;
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DB',
'Kernel::System::Encode',
'Kernel::System::Log',
'Kernel::System::Main',
);
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
return $Self;
}
sub FormIDCreate {
my ( $Self, %Param ) = @_;
# return requested form id
return time() . '.' . rand(12341241);
}
sub FormIDRemove {
my ( $Self, %Param ) = @_;
for (qw(FormID)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!"
);
return;
}
}
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => '
DELETE FROM web_upload_cache
WHERE form_id = ?',
Bind => [ \$Param{FormID} ],
);
return 1;
}
sub FormIDAddFile {
my ( $Self, %Param ) = @_;
for (qw(FormID Filename ContentType)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!"
);
return;
}
}
$Param{Content} = '' if !defined( $Param{Content} );
# get file 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} );
}
# create content id
my $ContentID = $Param{ContentID};
my $Disposition = $Param{Disposition} || '';
if ( !$ContentID && lc $Disposition eq 'inline' ) {
my $Random = rand 999999;
my $FQDN = $Kernel::OM->Get('Kernel::Config')->Get('FQDN');
$ContentID = "$Disposition$Random.$Param{FormID}\@$FQDN";
}
# write attachment to db
my $Time = time();
return if !$DBObject->Do(
SQL => '
INSERT INTO web_upload_cache (form_id, filename, content_type, content_size, content,
create_time_unix, content_id, disposition)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
Bind => [
\$Param{FormID}, \$Param{Filename}, \$Param{ContentType}, \$Param{Filesize},
\$Param{Content}, \$Time, \$ContentID, \$Param{Disposition}
],
);
return 1;
}
sub FormIDRemoveFile {
my ( $Self, %Param ) = @_;
for (qw(FormID FileID)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!"
);
return;
}
}
my @Index = @{ $Self->FormIDGetAllFilesMeta(%Param) };
# finish if files have been already removed by other process
return if !@Index;
my $ID = $Param{FileID} - 1;
$Param{Filename} = $Index[$ID]->{Filename};
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => '
DELETE FROM web_upload_cache
WHERE form_id = ?
AND filename = ?',
Bind => [ \$Param{FormID}, \$Param{Filename} ],
);
return 1;
}
sub FormIDGetAllFilesData {
my ( $Self, %Param ) = @_;
my $Counter = 0;
my @Data;
for (qw(FormID)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!"
);
return;
}
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
$DBObject->Prepare(
SQL => '
SELECT filename, content_type, content_size, content, content_id, disposition
FROM web_upload_cache
WHERE form_id = ?
ORDER BY create_time_unix',
Bind => [ \$Param{FormID} ],
Encode => [ 1, 1, 1, 0, 1, 1 ],
);
while ( my @Row = $DBObject->FetchrowArray() ) {
$Counter++;
# encode attachment if it's a postgresql backend!!!
if ( !$DBObject->GetDatabaseFunction('DirectBlob') ) {
$Row[3] = decode_base64( $Row[3] );
}
# add the info
push(
@Data,
{
Content => $Row[3],
ContentID => $Row[4],
ContentType => $Row[1],
Filename => $Row[0],
Filesize => $Row[2],
Disposition => $Row[5],
FileID => $Counter,
}
);
}
return \@Data;
}
sub FormIDGetAllFilesMeta {
my ( $Self, %Param ) = @_;
my $Counter = 0;
my @Data;
for (qw(FormID)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!"
);
return;
}
}
# get database object
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
$DBObject->Prepare(
SQL => '
SELECT filename, content_type, content_size, content_id, disposition
FROM web_upload_cache
WHERE form_id = ?
ORDER BY create_time_unix',
Bind => [ \$Param{FormID} ],
);
while ( my @Row = $DBObject->FetchrowArray() ) {
$Counter++;
# add the info
push(
@Data,
{
ContentID => $Row[3],
ContentType => $Row[1],
Filename => $Row[0],
Filesize => $Row[2],
Disposition => $Row[4],
FileID => $Counter,
}
);
}
return \@Data;
}
sub FormIDCleanUp {
my ( $Self, %Param ) = @_;
my $CurrentTile = time() - ( 60 * 60 * 24 * 1 );
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => '
DELETE FROM web_upload_cache
WHERE create_time_unix < ?',
Bind => [ \$CurrentTile ],
);
return 1;
}
1;

View File

@@ -0,0 +1,485 @@
# --
# 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::Web::UploadCache::FS;
use strict;
use warnings;
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::Log',
'Kernel::System::Main',
);
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
$Self->{TempDir} = $Kernel::OM->Get('Kernel::Config')->Get('TempDir') . '/upload_cache';
if ( !-d $Self->{TempDir} ) {
mkdir $Self->{TempDir};
}
return $Self;
}
sub FormIDCreate {
my ( $Self, %Param ) = @_;
# return requested form id
return time() . '.' . rand(12341241);
}
sub FormIDRemove {
my ( $Self, %Param ) = @_;
if ( !$Param{FormID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need FormID!'
);
return;
}
return if !$Self->_FormIDValidate( $Param{FormID} );
my $Directory = $Self->{TempDir} . '/' . $Param{FormID};
if ( !-d $Directory ) {
return 1;
}
# get main object
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my @List = $MainObject->DirectoryRead(
Directory => $Directory,
Filter => "*",
);
my @Data;
for my $File (@List) {
$MainObject->FileDelete(
Location => $File,
);
}
if ( !rmdir($Directory) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Can't remove: $Directory: $!!",
);
}
return 1;
}
sub FormIDAddFile {
my ( $Self, %Param ) = @_;
for (qw(FormID Filename ContentType)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!"
);
return;
}
}
return if !$Self->_FormIDValidate( $Param{FormID} );
$Param{Content} = '' if !defined( $Param{Content} );
# create content id
my $ContentID = $Param{ContentID};
my $Disposition = $Param{Disposition} || '';
if ( !$ContentID && lc $Disposition eq 'inline' ) {
my $Random = rand 999999;
my $FQDN = $Kernel::OM->Get('Kernel::Config')->Get('FQDN');
$ContentID = "$Disposition$Random.$Param{FormID}\@$FQDN";
}
# create cache subdirectory if not exist
my $Directory = $Self->{TempDir} . '/' . $Param{FormID};
if ( !-d $Directory ) {
# Create directory. This could fail if another process creates the
# same directory, so don't use the return value.
File::Path::mkpath( $Directory, 0, 0770 ); ## no critic
if ( !-d $Directory ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Can't create directory '$Directory': $!",
);
return;
}
}
# get main object
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
# files must readable for creator
return if !$MainObject->FileWrite(
Directory => $Directory,
Filename => "$Param{Filename}",
Content => \$Param{Content},
Mode => 'binmode',
Permission => '640',
NoReplace => 1,
);
return if !$MainObject->FileWrite(
Directory => $Directory,
Filename => "$Param{Filename}.ContentType",
Content => \$Param{ContentType},
Mode => 'binmode',
Permission => '640',
NoReplace => 1,
);
return if !$MainObject->FileWrite(
Directory => $Directory,
Filename => "$Param{Filename}.ContentID",
Content => \$ContentID,
Mode => 'binmode',
Permission => '640',
NoReplace => 1,
);
return if !$MainObject->FileWrite(
Directory => $Directory,
Filename => "$Param{Filename}.Disposition",
Content => \$Disposition,
Mode => 'binmode',
Permission => '644',
NoReplace => 1,
);
return 1;
}
sub FormIDRemoveFile {
my ( $Self, %Param ) = @_;
for (qw(FormID FileID)) {
if ( !$Param{$_} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $_!"
);
return;
}
}
return if !$Self->_FormIDValidate( $Param{FormID} );
my @Index = @{ $Self->FormIDGetAllFilesMeta(%Param) };
# finish if files have been already removed by other process
return if !@Index;
my $ID = $Param{FileID} - 1;
my %File = %{ $Index[$ID] };
my $Directory = $Self->{TempDir} . '/' . $Param{FormID};
if ( !-d $Directory ) {
return 1;
}
# get main object
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
$MainObject->FileDelete(
Directory => $Directory,
Filename => "$File{Filename}",
NoReplace => 1,
);
$MainObject->FileDelete(
Directory => $Directory,
Filename => "$File{Filename}.ContentType",
NoReplace => 1,
);
$MainObject->FileDelete(
Directory => $Directory,
Filename => "$File{Filename}.ContentID",
NoReplace => 1,
);
$MainObject->FileDelete(
Directory => $Directory,
Filename => "$File{Filename}.Disposition",
NoReplace => 1,
);
return 1;
}
sub FormIDGetAllFilesData {
my ( $Self, %Param ) = @_;
if ( !$Param{FormID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need FormID!'
);
return;
}
my @Data;
return \@Data if !$Self->_FormIDValidate( $Param{FormID} );
my $Directory = $Self->{TempDir} . '/' . $Param{FormID};
if ( !-d $Directory ) {
return \@Data;
}
# get main object
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my @List = $MainObject->DirectoryRead(
Directory => $Directory,
Filter => "*",
);
my $Counter = 0;
FILE:
for my $File (@List) {
# ignore meta files
next FILE if $File =~ /\.ContentType$/;
next FILE if $File =~ /\.ContentID$/;
next FILE if $File =~ /\.Disposition$/;
$Counter++;
my $FileSize = -s $File;
# human readable file size
if ( defined $FileSize ) {
# remove meta data in files
if ( $FileSize > 30 ) {
$FileSize = $FileSize - 30;
}
}
my $Content = $MainObject->FileRead(
Location => $File,
Mode => 'binmode', # optional - binmode|utf8
);
next FILE if !$Content;
my $ContentType = $MainObject->FileRead(
Location => "$File.ContentType",
Mode => 'binmode', # optional - binmode|utf8
);
next FILE if !$ContentType;
my $ContentID = $MainObject->FileRead(
Location => "$File.ContentID",
Mode => 'binmode', # optional - binmode|utf8
);
next FILE if !$ContentID;
# verify if content id is empty, set to undef
if ( !${$ContentID} ) {
${$ContentID} = undef;
}
my $Disposition = $MainObject->FileRead(
Location => "$File.Disposition",
Mode => 'binmode', # optional - binmode|utf8
);
next FILE if !$Disposition;
# strip filename
$File =~ s/^.*\/(.+?)$/$1/;
push(
@Data,
{
Content => ${$Content},
ContentID => ${$ContentID},
ContentType => ${$ContentType},
Filename => $File,
Filesize => $FileSize,
FileID => $Counter,
Disposition => ${$Disposition},
},
);
}
return \@Data;
}
sub FormIDGetAllFilesMeta {
my ( $Self, %Param ) = @_;
if ( !$Param{FormID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need FormID!'
);
return;
}
my @Data;
return \@Data if !$Self->_FormIDValidate( $Param{FormID} );
my $Directory = $Self->{TempDir} . '/' . $Param{FormID};
if ( !-d $Directory ) {
return \@Data;
}
# get main object
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my @List = $MainObject->DirectoryRead(
Directory => $Directory,
Filter => "*",
);
my $Counter = 0;
FILE:
for my $File (@List) {
# ignore meta files
next FILE if $File =~ /\.ContentType$/;
next FILE if $File =~ /\.ContentID$/;
next FILE if $File =~ /\.Disposition$/;
$Counter++;
my $FileSize = -s $File;
# human readable file size
if ( defined $FileSize ) {
# remove meta data in files
if ( $FileSize > 30 ) {
$FileSize = $FileSize - 30;
}
}
my $ContentType = $MainObject->FileRead(
Location => "$File.ContentType",
Mode => 'binmode', # optional - binmode|utf8
);
next FILE if !$ContentType;
my $ContentID = $MainObject->FileRead(
Location => "$File.ContentID",
Mode => 'binmode', # optional - binmode|utf8
);
next FILE if !$ContentID;
# verify if content id is empty, set to undef
if ( !${$ContentID} ) {
${$ContentID} = undef;
}
my $Disposition = $MainObject->FileRead(
Location => "$File.Disposition",
Mode => 'binmode', # optional - binmode|utf8
);
next FILE if !$Disposition;
# strip filename
$File =~ s/^.*\/(.+?)$/$1/;
push(
@Data,
{
ContentID => ${$ContentID},
ContentType => ${$ContentType},
Filename => $File,
Filesize => $FileSize,
FileID => $Counter,
Disposition => ${$Disposition},
},
);
}
return \@Data;
}
sub FormIDCleanUp {
my ( $Self, %Param ) = @_;
# get main object
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
my $RetentionTime = int( time() - 86400 ); # remove subdirs older than 24h
my @List = $MainObject->DirectoryRead(
Directory => $Self->{TempDir},
Filter => '*'
);
SUBDIR:
for my $Subdir (@List) {
my $SubdirTime = $Subdir;
if ( $SubdirTime =~ /^.*\/\d+\..+$/ ) {
$SubdirTime =~ s/^.*\/(\d+?)\..+$/$1/;
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message =>
"Won't delete upload cache directory $Subdir: timestamp in directory name not found! Please fix it manually.",
);
next SUBDIR;
}
if ( $RetentionTime > $SubdirTime ) {
my @Sublist = $MainObject->DirectoryRead(
Directory => $Subdir,
Filter => "*",
);
for my $File (@Sublist) {
$MainObject->FileDelete(
Location => $File,
);
}
if ( !rmdir($Subdir) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Can't remove: $Subdir: $!!",
);
next SUBDIR;
}
}
}
return 1;
}
sub _FormIDValidate {
my ( $Self, $FormID ) = @_;
return if !$FormID;
if ( $FormID !~ m{^ \d+ \. \d+ \. \d+ $}xms ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Invalid FormID!',
);
return;
}
return 1;
}
1;