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

849 lines
27 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::GenericInterface::Operation::ConfigItem::ConfigItemUpdate;
use strict;
use warnings;
## nofilter(TidyAll::Plugin::OTRS::Migrations::OTRS6::SysConfig)
use Kernel::System::VariableCheck qw(:all);
use parent qw(
Kernel::GenericInterface::Operation::Common
Kernel::GenericInterface::Operation::ConfigItem::Common
);
our $ObjectManagerDisabled = 1;
=head1 NAME
Kernel::GenericInterface::Operation::ConfigItem::ConfigItemUpdate - GenericInterface ConfigItem ConfigItemUpdate Operation backend
=head1 PUBLIC INTERFACE
=head2 new()
usually, you want to create an instance of this
by using Kernel::GenericInterface::Operation->new();
=cut
sub new {
my ( $Type, %Param ) = @_;
my $Self = {};
bless( $Self, $Type );
# check needed objects
for my $Needed (qw(DebuggerObject WebserviceID)) {
if ( !$Param{$Needed} ) {
return {
Success => 0,
ErrorMessage => "Got no $Needed!",
};
}
$Self->{$Needed} = $Param{$Needed};
}
# define operation name
$Self->{OperationName} = 'ConfigItemUpdate';
$Self->{Config} = $Kernel::OM->Get('Kernel::Config')->Get('GenericInterface::Operation::ConfigItemUpdate');
$Self->{Config}->{DefaultValue} = 'Not Defined';
my $GeneralCatalogObject = $Kernel::OM->Get('Kernel::System::GeneralCatalog');
# get a list of all config item classes
$Self->{ClassList} = $GeneralCatalogObject->ItemList(
Class => 'ITSM::ConfigItem::Class',
);
if ( !IsHashRefWithData( $Self->{ClassList} ) ) {
return $Self->{DebuggerObject}->Error(
Summary => 'Error when trying to get class listing of ITSM::ConfigItem::Class',
);
}
# get a list of all incistates
$Self->{InciStateList} = $GeneralCatalogObject->ItemList(
Class => 'ITSM::Core::IncidentState',
);
if ( !IsHashRefWithData( $Self->{InciStateList} ) ) {
return $Self->{DebuggerObject}->Error(
Summary => 'Error when trying to get incident state listing of'
. ' ITSM::Core::IncidentState',
);
}
# get a list of all deplstates
$Self->{DeplStateList} = $GeneralCatalogObject->ItemList(
Class => 'ITSM::ConfigItem::DeploymentState',
);
if ( !IsHashRefWithData( $Self->{DeplStateList} ) ) {
return $Self->{DebuggerObject}->Error(
Summary => 'Error when trying to get incident state listing of'
. ' ITSM::ConfigItem::DeploymentState',
);
}
# also provide the classlist in reversed form for easier reverse lookups
my %ReverseClassList = reverse %{ $Self->{ClassList} };
$Self->{ReverseClassList} = \%ReverseClassList;
# also provide the incistatelist in reversed form for easier reverse lookups
my %ReverseInciStateList = reverse %{ $Self->{InciStateList} };
$Self->{ReverseInciStateList} = \%ReverseInciStateList;
# also provide the deplstatelist in reversed form for easier reverse lookups
my %ReverseDeplStateList = reverse %{ $Self->{DeplStateList} };
$Self->{ReverseDeplStateList} = \%ReverseDeplStateList;
return $Self;
}
=head2 Run()
perform ConfigItemUpdate Operation. This will return the updated config item number.
my $Result = $OperationObject->Run(
Data => {
UserLogin => 'some agent login', # UserLogin or SessionID is
SessionID => 123, # required
Password => 'some password', # if UserLogin is sent then Password is required
ReplaceExistingData => 0, # optional, 0 or 1, default 0
# this will replace the existing XML data and attachments
ConfigItemID => 123,
ConfigItem => {
Class => 'Config Item Class',
Name => 'The Name',
DeplState => 'deployment state',
InciState => 'incident state',
CIXMLData => $ArrayHashRef, # it depends on the Configuration Item class and definition
Attachment => [
{
Content => 'content' # base64 encoded
ContentType => 'some content type'
Filename => 'some fine name'
},
# ...
],
# or
#Attachment => {
# Content => 'content'
# ContentType => 'some content type'
# Filename => 'some fine name'
#},
},
},
);
$Result = {
Success => 1, # 0 or 1
ErrorMessage => '', # in case of error
Data => { # result data payload after Operation
ConfigItemID => 123, # Configuration Item ID number in OTRS::ITSM (Service desk system)
Number => 2324454323322 # Configuration Item Number in OTRS::ITSM (Service desk system)
Error => { # should not return errors
ErrorCode => 'ConfigItemUpdate.ErrorCode'
ErrorMessage => 'Error Description'
},
},
};
=cut
sub Run {
my ( $Self, %Param ) = @_;
my $Result = $Self->Init(
WebserviceID => $Self->{WebserviceID},
);
if ( !$Result->{Success} ) {
$Self->ReturnError(
ErrorCode => 'Webservice.InvalidConfiguration',
ErrorMessage => $Result->{ErrorMessage},
);
}
# check needed stuff
if (
!$Param{Data}->{UserLogin}
&& !$Param{Data}->{SessionID}
)
{
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.MissingParameter",
ErrorMessage =>
"$Self->{OperationName}: UserLogin or SessionID is required!",
);
}
if ( $Param{Data}->{UserLogin} ) {
if ( !$Param{Data}->{Password} )
{
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.MissingParameter",
ErrorMessage => "$Self->{OperationName}: Password or SessionID is required!",
);
}
}
# authenticate user
my ( $UserID, $UserType ) = $Self->Auth(%Param);
if ( !$UserID ) {
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.AuthFail",
ErrorMessage => "$Self->{OperationName}: User could not be authenticated!",
);
}
# check needed hashes
for my $Needed (qw(ConfigItem)) {
if ( !IsHashRefWithData( $Param{Data}->{$Needed} ) ) {
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.MissingParameter",
ErrorMessage =>
"$Self->{OperationName}: $Needed parameter is missing or not valid!",
);
}
}
# check needed items
for my $Needed (qw(ConfigItemID)) {
if ( !IsPositiveInteger( $Param{Data}->{$Needed} ) ) {
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.MissingParameter",
ErrorMessage =>
"$Self->{OperationName}: $Needed parameter is missing or not valid!",
);
}
}
# check for valid ConfigItemID
my $ConfigItemID = $Param{Data}->{ConfigItemID};
# get config item object
my $ConfigItemObject = $Kernel::OM->Get('Kernel::System::ITSMConfigItem');
# get ConfigItem data
my $ConfigItemData = $ConfigItemObject->ConfigItemGet(
ConfigItemID => $ConfigItemID,
);
if ( !IsHashRefWithData($ConfigItemData) ) {
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.InvalidParameter",
ErrorMessage => "$Self->{OperationName}: ConfigItemID is invalid!",
);
}
# isolate config item parameter
my $ConfigItem = $Param{Data}->{ConfigItem};
# remove leading and trailing spaces
for my $Attribute ( sort keys %{$ConfigItem} ) {
if ( ref $Attribute ne 'HASH' && ref $Attribute ne 'ARRAY' ) {
# remove leading spaces
$ConfigItem->{$Attribute} =~ s{\A\s+}{};
# remove trailing spaces
$ConfigItem->{$Attribute} =~ s{\s+\z}{};
}
}
# if the parameter ReplaceExistingData is set to 0 or if it is missing
# then missing, empty or only partially defined CIXMLData parameter attributes are allowed
# in this case the existing CIXMLData is used for the missing parts.
# A missing (undefined) CIXMLData attribute has the same effect
# the ReplaceExistingData parameter also influences if existing attachments should be replaced or kept
if ( !$Param{Data}->{ReplaceExistingData} || !defined $ConfigItem->{CIXMLData} ) {
# set to empty hash reference if empty or not defined
$ConfigItem->{CIXMLData} ||= {};
# CIXMLData must be a hash reference
if ( ref $ConfigItem->{CIXMLData} ne 'HASH' ) {
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.MissingParameter",
ErrorMessage => "$Self->{OperationName}: ConfigItem->CIXMLData is missing or invalid!",
);
}
# get latest version data from configitem
my $Version = $ConfigItemObject->VersionGet(
ConfigItemID => $ConfigItemID,
UserID => $UserID,
);
if ( !IsHashRefWithData($Version) ) {
my $ErrorMessage = 'Could not get ConfigItem data'
. ' in Kernel::GenericInterface::Operation::ConfigItem::ConfigItemUpdate::Run()';
return $Self->ReturnError(
ErrorCode => '$Self->{OperationName}.InvalidParameter',
ErrorMessage => "$Self->{OperationName}: $ErrorMessage",
);
}
# remove unneeded items
delete $Version->{ClassID};
delete $Version->{CurDeplStateID};
delete $Version->{CurInciStateID};
delete $Version->{DeplStateID};
delete $Version->{InciStateID};
delete $Version->{XMLDefinitionID};
my $Definition = delete $Version->{XMLDefinition};
my $FormatedXMLData = $Self->InvertFormatXMLData(
XMLData => $Version->{XMLData}->[1]->{Version},
);
my $ReplacedXMLData = $Self->InvertReplaceXMLData(
XMLData => $FormatedXMLData,
Definition => $Definition,
);
$Version->{XMLData} = $ReplacedXMLData;
# rename XMLData since SOAP transport complains about XML prefix on names
$Version->{CIXMLData} = delete $Version->{XMLData};
# merge existing data and new data from parameters
$ConfigItem->{CIXMLData} = {
%{ $Version->{CIXMLData} },
%{ $ConfigItem->{CIXMLData} },
};
}
if ( !IsHashRefWithData( $ConfigItem->{CIXMLData} ) ) {
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.InvalidParameter",
ErrorMessage => "$Self->{OperationName}: ConfigItem->CIXMLData is empty or invalid!",
);
}
# remove leading and trailing spaces for CIXMLData
$Self->_CleanXMLData( XMLData => $ConfigItem->{CIXMLData} );
# check ConfigItem attribute values
my $ConfigItemCheck = $Self->_CheckConfigItem( ConfigItem => $ConfigItem );
if ( !$ConfigItemCheck->{Success} ) {
return $Self->ReturnError( %{$ConfigItemCheck} );
}
# check update permissions
my $Permission = $ConfigItemObject->Permission(
Scope => 'Class',
ClassID => $Self->{ReverseClassList}->{ $ConfigItem->{Class} },
UserID => $UserID,
Type => $Self->{Config}->{Permission},
);
if ( !$Permission ) {
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.AccessDenied",
ErrorMessage => "$Self->{OperationName}: Can not update configuration items!",
);
}
# handle attachments
my $Attachment;
my @AttachmentList;
if ( defined $Param{Data}->{ConfigItem}->{Attachment} ) {
# isolate Attachment parameter
$Attachment = delete $Param{Data}->{ConfigItem}->{Attachment};
# homologate imput to array
if ( IsHashRefWithData($Attachment) ) {
push @AttachmentList, $Attachment;
}
elsif ( IsArrayRefWithData($Attachment) ) {
@AttachmentList = @{$Attachment};
}
else {
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.InvalidParameter",
ErrorMessage =>
"$Self->{OperationName}: ConfigItem->Attachment parameter is invalid!",
);
}
# check Attachment internal structure
for my $AttachmentItem (@AttachmentList) {
if ( !IsHashRefWithData($AttachmentItem) ) {
return $Self->ReturnError(
ErrorCode => "$Self->{OperationName}.InvalidParameter",
ErrorMessage =>
"$Self->{OperationName}: ConfigItem->Attachment parameter is invalid!",
);
}
# remove leading and trailing spaces
for my $Attribute ( sort keys %{$AttachmentItem} ) {
if ( ref $Attribute ne 'HASH' && ref $Attribute ne 'ARRAY' ) {
#remove leading spaces
$AttachmentItem->{$Attribute} =~ s{\A\s+}{};
#remove trailing spaces
$AttachmentItem->{$Attribute} =~ s{\s+\z}{};
}
}
# check Attachment attribute values
my $AttachmentCheck = $Self->_CheckAttachment( Attachment => $AttachmentItem );
if ( !$AttachmentCheck->{Success} ) {
return $Self->ReturnError( %{$AttachmentCheck} );
}
}
}
return $Self->_ConfigItemUpdate(
ConfigItem => $ConfigItem,
ConfigItemID => $ConfigItemID,
AttachmentList => \@AttachmentList,
ReplaceExistingData => $Param{Data}->{ReplaceExistingData},
UserID => $UserID,
);
}
=head1 INTERNAL INTERFACE
=head2 _CleanXMLData()
removed trailing and leading white spaces in the XMLData.
my $XMLDataClean = $OperationObject->_CleanXMLData(
Definition => $DefinitionArrayRef, # Config Item Definition ot just part of it
XMLData => $XMLDataHashRef,
);
returns:
$XMLDataClean = {
Success => 1, # if everything is OK
}
$XMLDataClean = {
ErrorCode => 'Function.Error', # if error
ErrorMessage => 'Error description',
}
=cut
sub _CleanXMLData {
my ( $Self, %Param ) = @_;
my $XMLData = $Param{XMLData};
KEY:
for my $Key ( sort keys %{$XMLData} ) {
if ( ref $XMLData->{$Key} eq 'ARRAY' ) {
ELEMENT:
for my $Element ( @{ $XMLData->{$Key} } ) {
if ( ref $Element eq 'HASH' ) {
# start recursion
$Self->_CleanXMLData( XMLData => $Element );
next ELEMENT;
}
elsif ( ref $Element eq '' ) {
#remove leading spaces
$Element =~ s{\A\s+}{};
#remove trailing spaces
$Element =~ s{\s+\z}{};
}
}
}
elsif ( ref $XMLData->{$Key} eq 'HASH' ) {
# start recursion
$Self->_CleanXMLData( XMLData => $XMLData->{$Key} );
next KEY;
}
elsif ( ref $XMLData->{$Key} eq '' ) {
# TODO: Use StringClean function!
#remove leading spaces
$XMLData->{$Key} =~ s{\A\s+}{};
#remove trailing spaces
$XMLData->{$Key} =~ s{\s+\z}{};
}
}
return 1;
}
=head2 _CheckConfigItem()
checks if the given config item parameters are valid.
my $ConfigItemCheck = $OperationObject->_CheckConfigItem(
ConfigItem => $ConfigItem, # all config item parameters
);
returns:
$ConfigItemCheck = {
Success => 1, # if everything is OK
}
$ConfigItemCheck = {
ErrorCode => 'Function.Error', # if error
ErrorMessage => 'Error description',
}
=cut
sub _CheckConfigItem {
my ( $Self, %Param ) = @_;
my $ConfigItem = $Param{ConfigItem};
# check config item internally
for my $Needed (qw(Class Name DeplState InciState CIXMLData)) {
if ( !$ConfigItem->{$Needed} ) {
return {
ErrorCode => "$Self->{OperationName}.MissingParameter",
ErrorMessage => "$Self->{OperationName}: ConfigItem->$Needed parameter is missing!",
};
}
}
# check ConfigItem->Class
if ( !$Self->ValidateClass( %{$ConfigItem} ) ) {
return {
ErrorCode => "$Self->{OperationName}.InvalidParameter",
ErrorMessage =>
"$Self->{OperationName}: ConfigItem->Class parameter is invalid!",
};
}
# check ConfigItem->DeplState
if ( !$Self->ValidateDeplState( %{$ConfigItem} ) ) {
return {
ErrorCode => "$Self->{OperationName}.InvalidParameter",
ErrorMessage =>
"$Self->{OperationName}: ConfigItem->DeplState parameter is invalid!",
};
}
# check ConfigItem->DeplState
if ( !$Self->ValidateInciState( %{$ConfigItem} ) ) {
return {
ErrorCode => "$Self->{OperationName}.InvalidParameter",
ErrorMessage =>
"$Self->{OperationName}: ConfigItem->InciState parameter is invalid!",
};
}
# get last config item defintion
my $DefinitionData = $Kernel::OM->Get('Kernel::System::ITSMConfigItem')->DefinitionGet(
ClassID => $Self->{ReverseClassList}->{ $ConfigItem->{Class} },
);
my $XMLDataCheckResult = $Self->CheckXMLData(
Definition => $DefinitionData->{DefinitionRef},
XMLData => $ConfigItem->{CIXMLData},
);
if ( !$XMLDataCheckResult->{Success} ) {
return $XMLDataCheckResult;
}
# if everything is OK then return Success
return {
Success => 1,
};
}
=head2 _CheckAttachment()
checks if the given attachment parameter is valid.
my $AttachmentCheck = $OperationObject->_CheckAttachment(
Attachment => $Attachment, # all attachment parameters
);
returns:
$AttachmentCheck = {
Success => 1, # if everething is OK
}
$AttachmentCheck = {
ErrorCode => 'Function.Error', # if error
ErrorMessage => 'Error description',
}
=cut
sub _CheckAttachment {
my ( $Self, %Param ) = @_;
my $Attachment = $Param{Attachment};
# check attachment item internally
for my $Needed (qw(Content ContentType Filename)) {
if ( !$Attachment->{$Needed} ) {
return {
ErrorCode => "$Self->{OperationName}.MissingParameter",
ErrorMessage =>
"$Self->{OperationName}: Attachment->$Needed parameter is missing!",
};
}
}
# check Article->ContentType
if ( $Attachment->{ContentType} ) {
$Attachment->{ContentType} = lc $Attachment->{ContentType};
# check Charset part
my $Charset = '';
if ( $Attachment->{ContentType} =~ /charset=/i ) {
$Charset = $Attachment->{ContentType};
$Charset =~ s/.+?charset=("|'|)(\w+)/$2/gi;
$Charset =~ s/"|'//g;
$Charset =~ s/(.+?);.*/$1/g;
}
if ( $Charset && !$Self->ValidateCharset( Charset => $Charset ) )
{
return {
ErrorCode => "$Self->{OperationName}.InvalidParameter",
ErrorMessage => "$Self->{OperationName}: Attachment->ContentType is invalid!",
};
}
# check MimeType part
my $MimeType = '';
if ( $Attachment->{ContentType} =~ /^(\w+\/\w+)/i ) {
$MimeType = $1;
$MimeType =~ s/"|'//g;
}
if ( !$Self->ValidateMimeType( MimeType => $MimeType ) ) {
return {
ErrorCode => "$Self->{OperationName}.InvalidParameter",
ErrorMessage => "$Self->{OperationName}: Attachment->ContentType is invalid!",
};
}
}
# if everything is OK then return Success
return {
Success => 1,
};
}
=head2 _ConfigItemUpdate()
updates a configuration item with attachments if specified.
my $Response = $OperationObject->_ConfigItemUpdate(
ConfigItemID => 123,
ConfigItem => $ConfigItem, # all configuration item parameters
AttachmentList => $Attachment, # a list of all attachments
ReplaceExistingData => 0, # if the existing xml attributes and attachments should be replaced or kept
UserID => 123,
);
returns:
$Response = {
Success => 1, # if everething is OK
Data => {
ConfigItemID => 123,
ConfigItemNumber => 'CN123',
}
}
$Response = {
Success => 0, # if unexpected error
ErrorMessage => "$Param{ErrorCode}: $Param{ErrorMessage}",
}
=cut
sub _ConfigItemUpdate {
my ( $Self, %Param ) = @_;
my $ConfigItemID = $Param{ConfigItemID};
my $ConfigItem = $Param{ConfigItem};
my $AttachmentList = $Param{AttachmentList};
my $DeplStateID = $Self->{ReverseDeplStateList}->{ $ConfigItem->{DeplState} };
my $InciStateID = $Self->{ReverseInciStateList}->{ $ConfigItem->{InciState} };
my $RawXMLData = $ConfigItem->{CIXMLData};
# get config item object
my $ConfigItemObject = $Kernel::OM->Get('Kernel::System::ITSMConfigItem');
# get last config item defintion
my $DefinitionData = $ConfigItemObject->DefinitionGet(
ClassID => $Self->{ReverseClassList}->{ $ConfigItem->{Class} },
);
# replace date, date time, customer, company and general catalog values
my $ReplacedXMLData = $Self->ReplaceXMLData(
XMLData => $RawXMLData,
Definition => $DefinitionData->{DefinitionRef},
);
# create an XMLData structure suitable for VersionAdd
my $XMLData = $Self->FormatXMLData(
XMLData => $ReplacedXMLData,
);
# get the current config item version data
my $CurrentVersion = $ConfigItemObject->VersionGet(
ConfigItemID => $ConfigItemID,
UserID => $Param{UserID},
);
my $VersionID = $ConfigItemObject->VersionAdd(
ConfigItemID => $ConfigItemID,
Name => $ConfigItem->{Name},
DefinitionID => $DefinitionData->{DefinitionID},
DeplStateID => $DeplStateID,
InciStateID => $InciStateID,
XMLData => $XMLData,
UserID => $Param{UserID},
);
if ( !$VersionID ) {
return {
Success => 0,
ErrorMessage => 'Configuration Item could not be updated, please contact the system'
. 'administrator'
};
}
# get the version ID of the config item before the update
my $CurrentVersionID = $CurrentVersion->{VersionID} || '';
# compare old version and new version IDs
if ( $CurrentVersionID eq $VersionID ) {
$Self->{DebuggerObject}->Notice(
Summary => "$Self->{OperationName}: No change in configuration item version",
Data => 'The internal structure of the configuration item was indentical to the last'
. ' one, no update was performed',
);
}
# the ReplaceExistingData flag is set
if ( $Param{ReplaceExistingData} ) {
# get a list of all attachments
my @ExistingAttachments = $ConfigItemObject->ConfigItemAttachmentList(
ConfigItemID => $ConfigItemID,
);
# delete all attachments of this config item
FILENAME:
for my $Filename (@ExistingAttachments) {
# delete the attachment
my $DeletionSuccess = $ConfigItemObject->ConfigItemAttachmentDelete(
ConfigItemID => $ConfigItemID,
Filename => $Filename,
UserID => $Param{UserID},
);
if ( !$DeletionSuccess ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Unknown problem when deleting attachment $Filename of ConfigItem "
. "$ConfigItemID. Please check the VirtualFS backend for stale files!",
);
}
}
}
# set attachments
if ( IsArrayRefWithData($AttachmentList) ) {
for my $Attachment ( @{$AttachmentList} ) {
my $Result = $Self->CreateAttachment(
Attachment => $Attachment,
ConfigItemID => $ConfigItemID,
UserID => $Param{UserID}
);
if ( !$Result->{Success} ) {
my $ErrorMessage = $Result->{ErrorMessage}
|| "Attachment could not be created, please contact the system administrator";
return {
Success => 0,
ErrorMessage => $ErrorMessage,
};
}
}
}
# get ConfigItem data
my $ConfigItemData = $ConfigItemObject->ConfigItemGet(
ConfigItemID => $ConfigItemID,
);
if ( !IsHashRefWithData($ConfigItemData) ) {
return {
Success => 0,
ErrorMessage => 'Could not get new configuration item information, please contact the system administrator',
};
}
return {
Success => 1,
Data => {
ConfigItemID => $ConfigItemID,
Number => $ConfigItemData->{Number},
},
};
}
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