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