Files
scripts/Perl OTRS/Kernel/System/ITSMConfigItem.pm
2024-10-14 00:08:40 +02:00

2003 lines
59 KiB
Perl

# --
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --
package Kernel::System::ITSMConfigItem;
use strict;
use warnings;
## nofilter(TidyAll::Plugin::OTRS::Migrations::OTRS6::SysConfig)
use Kernel::System::EventHandler;
use Kernel::System::ITSMConfigItem::Definition;
use Kernel::System::ITSMConfigItem::History;
use Kernel::System::ITSMConfigItem::Number;
use Kernel::System::ITSMConfigItem::Permission;
use Kernel::System::ITSMConfigItem::Version;
use Kernel::System::ITSMConfigItem::XML;
use Kernel::System::VariableCheck qw(:all);
use Storable;
use vars qw(@ISA);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::System::DB',
'Kernel::System::Cache',
'Kernel::System::GeneralCatalog',
'Kernel::System::LinkObject',
'Kernel::System::Log',
'Kernel::System::Main',
'Kernel::System::Service',
'Kernel::System::User',
'Kernel::System::VirtualFS',
'Kernel::System::XML',
);
=head1 NAME
Kernel::System::ITSMConfigItem - config item lib
=head1 DESCRIPTION
All config item functions.
=head1 PUBLIC INTERFACE
=head2 new()
create an object
use Kernel::System::ObjectManager;
local $Kernel::OM = Kernel::System::ObjectManager->new();
my $ConfigItemObject = $Kernel::OM->Get('Kernel::System::ITSMConfigItem');
=cut
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
$Self->{CacheType} = 'ITSMConfigurationManagement';
$Self->{CacheTTL} = 60 * 60 * 24 * 20;
@ISA = qw(
Kernel::System::ITSMConfigItem::Definition
Kernel::System::ITSMConfigItem::History
Kernel::System::ITSMConfigItem::Number
Kernel::System::ITSMConfigItem::Permission
Kernel::System::ITSMConfigItem::Version
Kernel::System::ITSMConfigItem::XML
Kernel::System::EventHandler
);
# init of event handler
$Self->EventHandlerInit(
Config => 'ITSMConfigItem::EventModulePost',
);
return $Self;
}
=head2 ConfigItemCount()
count all records of a config item class
my $Count = $ConfigItemObject->ConfigItemCount(
ClassID => 123,
);
=cut
sub ConfigItemCount {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{ClassID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need ClassID!',
);
return;
}
# get state list
my $StateList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
Class => 'ITSM::ConfigItem::DeploymentState',
Preferences => {
Functionality => [ 'preproductive', 'productive' ],
},
);
return 0 if !%{$StateList};
# create state string
my $DeplStateString = join q{, }, keys %{$StateList};
# ask database
$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => "SELECT COUNT(id) FROM configitem WHERE class_id = ? AND "
. "cur_depl_state_id IN ( $DeplStateString )",
Bind => [ \$Param{ClassID} ],
Limit => 1,
);
# fetch the result
my $Count = 0;
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
$Count = $Row[0];
}
return $Count;
}
=head2 ConfigItemResultList()
return a config item list as array hash reference
my $ConfigItemListRef = $ConfigItemObject->ConfigItemResultList(
ClassID => 123,
Start => 100,
Limit => 50,
);
=cut
sub ConfigItemResultList {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{ClassID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need ClassID!',
);
return;
}
# get state list
my $StateList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
Class => 'ITSM::ConfigItem::DeploymentState',
Preferences => {
Functionality => [ 'preproductive', 'productive' ],
},
);
# create state string
my $DeplStateString = join q{, }, keys %{$StateList};
# ask database
$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => "SELECT id FROM configitem "
. "WHERE class_id = ? AND cur_depl_state_id IN ( $DeplStateString ) "
. "ORDER BY change_time DESC",
Bind => [ \$Param{ClassID} ],
Start => $Param{Start},
Limit => $Param{Limit},
);
# fetch the result
my @ConfigItemIDList;
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
push @ConfigItemIDList, $Row[0];
}
# get last versions data
my @ConfigItemList;
for my $ConfigItemID (@ConfigItemIDList) {
# get version data
my $LastVersion = $Self->VersionGet(
ConfigItemID => $ConfigItemID,
XMLDataGet => 0,
);
push @ConfigItemList, $LastVersion;
}
return \@ConfigItemList;
}
=head2 ConfigItemGet()
return a config item as hash reference
my $ConfigItem = $ConfigItemObject->ConfigItemGet(
ConfigItemID => 123,
Cache => 0, # (optional) default 1 (0|1)
);
A hashref with the following keys is returned:
$ConfigItem{ConfigItemID}
$ConfigItem{Number}
$ConfigItem{ClassID}
$ConfigItem{Class}
$ConfigItem{LastVersionID}
$ConfigItem{CurDeplStateID}
$ConfigItem{CurDeplState}
$ConfigItem{CurDeplStateType}
$ConfigItem{CurInciStateID}
$ConfigItem{CurInciState}
$ConfigItem{CurInciStateType}
$ConfigItem{CreateTime}
$ConfigItem{CreateBy}
$ConfigItem{ChangeTime}
$ConfigItem{ChangeBy}
=cut
sub ConfigItemGet {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{ConfigItemID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need ConfigItemID!',
);
return;
}
# enable cache per default
if ( !defined $Param{Cache} ) {
$Param{Cache} = 1;
}
# check if result is already cached
my $CacheKey = 'ConfigItemGet::ConfigItemID::' . $Param{ConfigItemID};
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $Cache = $CacheObject->Get(
Type => $Self->{CacheType},
Key => $CacheKey,
);
return Storable::dclone($Cache) if $Cache;
# ask database
$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => 'SELECT id, configitem_number, class_id, last_version_id, '
. 'cur_depl_state_id, cur_inci_state_id, '
. 'create_time, create_by, change_time, change_by '
. 'FROM configitem WHERE id = ?',
Bind => [ \$Param{ConfigItemID} ],
Limit => 1,
);
# fetch the result
my %ConfigItem;
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
$ConfigItem{ConfigItemID} = $Row[0];
$ConfigItem{Number} = $Row[1];
$ConfigItem{ClassID} = $Row[2];
$ConfigItem{LastVersionID} = $Row[3];
$ConfigItem{CurDeplStateID} = $Row[4];
$ConfigItem{CurInciStateID} = $Row[5];
$ConfigItem{CreateTime} = $Row[6];
$ConfigItem{CreateBy} = $Row[7];
$ConfigItem{ChangeTime} = $Row[8];
$ConfigItem{ChangeBy} = $Row[9];
}
# check config item
if ( !$ConfigItem{ConfigItemID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "No such ConfigItemID ($Param{ConfigItemID})!",
);
return;
}
# get class list
my $ClassList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
Class => 'ITSM::ConfigItem::Class',
);
$ConfigItem{Class} = $ClassList->{ $ConfigItem{ClassID} };
return \%ConfigItem if !$ConfigItem{CurDeplStateID} || !$ConfigItem{CurInciStateID};
# get deployment state functionality
my $DeplState = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemGet(
ItemID => $ConfigItem{CurDeplStateID},
);
$ConfigItem{CurDeplState} = $DeplState->{Name};
$ConfigItem{CurDeplStateType} = $DeplState->{Functionality};
# get incident state functionality
my $InciState = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemGet(
ItemID => $ConfigItem{CurInciStateID},
);
$ConfigItem{CurInciState} = $InciState->{Name};
$ConfigItem{CurInciStateType} = $InciState->{Functionality};
# cache the result
$CacheObject->Set(
Type => $Self->{CacheType},
TTL => $Self->{CacheTTL},
Key => $CacheKey,
Value => Storable::dclone( \%ConfigItem ),
);
return \%ConfigItem;
}
=head2 ConfigItemAdd()
add a new config item
my $ConfigItemID = $ConfigItemObject->ConfigItemAdd(
Number => '111', # (optional)
ClassID => 123,
UserID => 1,
);
=cut
sub ConfigItemAdd {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ClassID UserID)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# get class list
my $ClassList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
Class => 'ITSM::ConfigItem::Class',
);
return if !$ClassList;
return if ref $ClassList ne 'HASH';
# check the class id
if ( !$ClassList->{ $Param{ClassID} } ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'No valid class id given!',
);
return;
}
# create config item number
if ( $Param{Number} ) {
# find existing config item number
my $Exists = $Self->ConfigItemNumberLookup(
ConfigItemNumber => $Param{Number},
);
if ($Exists) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Config item number already exists!',
);
return;
}
}
else {
# create config item number
$Param{Number} = $Self->ConfigItemNumberCreate(
Type => $Kernel::OM->Get('Kernel::Config')->Get('ITSMConfigItem::NumberGenerator'),
ClassID => $Param{ClassID},
);
}
# insert new config item
my $Success = $Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => 'INSERT INTO configitem '
. '(configitem_number, class_id, create_time, create_by, change_time, change_by) '
. 'VALUES (?, ?, current_timestamp, ?, current_timestamp, ?)',
Bind => [ \$Param{Number}, \$Param{ClassID}, \$Param{UserID}, \$Param{UserID} ],
);
return if !$Success;
# find id of new item
$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => 'SELECT id FROM configitem WHERE '
. 'configitem_number = ? AND class_id = ? ORDER BY id DESC',
Bind => [ \$Param{Number}, \$Param{ClassID} ],
Limit => 1,
);
# fetch the result
my $ConfigItemID;
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
$ConfigItemID = $Row[0];
}
# trigger ConfigItemCreate
$Self->EventHandler(
Event => 'ConfigItemCreate',
Data => {
ConfigItemID => $ConfigItemID,
Comment => $ConfigItemID . '%%' . $Param{Number},
},
UserID => $Param{UserID},
);
return $ConfigItemID;
}
=head2 ConfigItemDelete()
delete an existing config item
my $True = $ConfigItemObject->ConfigItemDelete(
ConfigItemID => 123,
UserID => 1,
);
=cut
sub ConfigItemDelete {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ConfigItemID UserID)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# remember config item data before delete
my $ConfigItemData = $Self->ConfigItemGet(
ConfigItemID => $Param{ConfigItemID},
);
# delete all links to this config item first, before deleting the versions
return if !$Kernel::OM->Get('Kernel::System::LinkObject')->LinkDeleteAll(
Object => 'ITSMConfigItem',
Key => $Param{ConfigItemID},
UserID => $Param{UserID},
);
# delete existing versions
$Self->VersionDelete(
ConfigItemID => $Param{ConfigItemID},
UserID => $Param{UserID},
);
# get a list of all attachments
my @ExistingAttachments = $Self->ConfigItemAttachmentList(
ConfigItemID => $Param{ConfigItemID},
);
# delete all attachments of this config item
FILENAME:
for my $Filename (@ExistingAttachments) {
# delete the attachment
my $DeletionSuccess = $Self->ConfigItemAttachmentDelete(
ConfigItemID => $Param{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 "
. "$Param{ConfigItemID}. Please check the VirtualFS backend for stale "
. "files!",
);
}
}
# trigger ConfigItemDelete event
# this must be done before deleting the config item from the database,
# because of a foreign key constraint in the configitem_history table
$Self->EventHandler(
Event => 'ConfigItemDelete',
Data => {
ConfigItemID => $Param{ConfigItemID},
Comment => $Param{ConfigItemID},
Number => $ConfigItemData->{Number},
Class => $ConfigItemData->{Class},
},
UserID => $Param{UserID},
);
# delete config item
my $Success = $Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => 'DELETE FROM configitem WHERE id = ?',
Bind => [ \$Param{ConfigItemID} ],
);
# delete the cache
my $CacheKey = 'ConfigItemGet::ConfigItemID::' . $Param{ConfigItemID};
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
Type => $Self->{CacheType},
Key => $CacheKey,
);
return $Success;
}
=head2 ConfigItemAttachmentAdd()
adds an attachment to a config item
my $Success = $ConfigItemObject->ConfigItemAttachmentAdd(
ConfigItemID => 1,
Filename => 'filename',
Content => 'content',
ContentType => 'text/plain',
UserID => 1,
);
=cut
sub ConfigItemAttachmentAdd {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(ConfigItemID Filename Content ContentType UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# write to virtual fs
my $Success = $Kernel::OM->Get('Kernel::System::VirtualFS')->Write(
Filename => "ConfigItem/$Param{ConfigItemID}/$Param{Filename}",
Mode => 'binary',
Content => \$Param{Content},
Preferences => {
ContentID => $Param{ContentID},
ContentType => $Param{ContentType},
ConfigItemID => $Param{ConfigItemID},
UserID => $Param{UserID},
},
);
# check for error
if ($Success) {
# trigger AttachmentAdd-Event
$Self->EventHandler(
Event => 'AttachmentAddPost',
Data => {
%Param,
ConfigItemID => $Param{ConfigItemID},
Comment => $Param{Filename},
HistoryType => 'AttachmentAdd',
},
UserID => $Param{UserID},
);
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Cannot add attachment for config item $Param{ConfigItemID}",
);
return;
}
return 1;
}
=head2 ConfigItemAttachmentDelete()
Delete the given file from the virtual filesystem.
my $Success = $ConfigItemObject->ConfigItemAttachmentDelete(
ConfigItemID => 123, # used in event handling, e.g. for logging the history
Filename => 'Projectplan.pdf', # identifies the attachment (together with the ConfigItemID)
UserID => 1,
);
=cut
sub ConfigItemAttachmentDelete {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(ConfigItemID Filename UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# add prefix
my $Filename = 'ConfigItem/' . $Param{ConfigItemID} . '/' . $Param{Filename};
# delete file
my $Success = $Kernel::OM->Get('Kernel::System::VirtualFS')->Delete(
Filename => $Filename,
);
# check for error
if ($Success) {
# trigger AttachmentDeletePost-Event
$Self->EventHandler(
Event => 'AttachmentDeletePost',
Data => {
%Param,
ConfigItemID => $Param{ConfigItemID},
Comment => $Param{Filename},
HistoryType => 'AttachmentDelete',
},
UserID => $Param{UserID},
);
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Cannot delete attachment $Filename!",
);
return;
}
return $Success;
}
=head2 ConfigItemAttachmentGet()
This method returns information about one specific attachment.
my $Attachment = $ConfigItemObject->ConfigItemAttachmentGet(
ConfigItemID => 4,
Filename => 'test.txt',
);
returns
{
Preferences => {
AllPreferences => 'test',
},
Filename => 'test.txt',
Content => 'content',
ContentType => 'text/plain',
Filesize => 12348409,
Type => 'attachment',
}
=cut
sub ConfigItemAttachmentGet {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Argument (qw(ConfigItemID Filename)) {
if ( !$Param{$Argument} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Argument!",
);
return;
}
}
# add prefix
my $Filename = 'ConfigItem/' . $Param{ConfigItemID} . '/' . $Param{Filename};
# find all attachments of this config item
my @Attachments = $Kernel::OM->Get('Kernel::System::VirtualFS')->Find(
Filename => $Filename,
Preferences => {
ConfigItemID => $Param{ConfigItemID},
},
);
# return error if file does not exist
if ( !@Attachments ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Message => "No such attachment ($Filename)!",
Priority => 'error',
);
return;
}
# get data for attachment
my %AttachmentData = $Kernel::OM->Get('Kernel::System::VirtualFS')->Read(
Filename => $Filename,
Mode => 'binary',
);
my $AttachmentInfo = {
%AttachmentData,
Filename => $Param{Filename},
Content => ${ $AttachmentData{Content} },
ContentType => $AttachmentData{Preferences}->{ContentType},
Type => 'attachment',
Filesize => $AttachmentData{Preferences}->{FilesizeRaw},
};
return $AttachmentInfo;
}
=head2 ConfigItemAttachmentList()
Returns an array with all attachments of the given config item.
my @Attachments = $ConfigItemObject->ConfigItemAttachmentList(
ConfigItemID => 123,
);
returns
@Attachments = (
'filename.txt',
'other_file.pdf',
);
=cut
sub ConfigItemAttachmentList {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{ConfigItemID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need ConfigItemID!',
);
return;
}
# find all attachments of this config item
my @Attachments = $Kernel::OM->Get('Kernel::System::VirtualFS')->Find(
Preferences => {
ConfigItemID => $Param{ConfigItemID},
},
);
for my $Filename (@Attachments) {
# remove extra information from filename
$Filename =~ s{ \A ConfigItem / \d+ / }{}xms;
}
return @Attachments;
}
=head2 ConfigItemAttachmentExists()
Checks if a file with a given filename exists.
my $Exists = $ConfigItemObject->ConfigItemAttachmentExists(
Filename => 'test.txt',
ConfigItemID => 123,
UserID => 1,
);
=cut
sub ConfigItemAttachmentExists {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(Filename ConfigItemID UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
return if !$Kernel::OM->Get('Kernel::System::VirtualFS')->Find(
Filename => 'ConfigItem/' . $Param{ConfigItemID} . '/' . $Param{Filename},
);
return 1;
}
=head2 ConfigItemSearchExtended()
return a config item list as an array reference
my $ConfigItemIDs = $ConfigItemObject->ConfigItemSearchExtended(
Number => 'The ConfigItem Number', # (optional)
Name => 'The Name', # (optional)
ClassIDs => [9, 8, 7, 6], # (optional)
DeplStateIDs => [1, 2, 3, 4], # (optional)
InciStateIDs => [1, 2, 3, 4], # (optional)
# config items with created time after ...
ConfigItemCreateTimeNewerDate => '2006-01-09 00:00:01', # (optional)
# config items with created time before then ....
ConfigItemCreateTimeOlderDate => '2006-01-19 23:59:59', # (optional)
# config items with changed time after ...
ConfigItemChangeTimeNewerDate => '2006-01-09 00:00:01', # (optional)
# config items with changed time before then ....
ConfigItemChangeTimeOlderDate => '2006-01-19 23:59:59', # (optional)
What => [ # (optional)
# each array element is a and condition
{
# or condition in hash
"[%]{'ElementA'}[%]{'ElementB'}[%]{'Content'}" => '%contentA%',
"[%]{'ElementA'}[%]{'ElementC'}[%]{'Content'}" => '%contentA%',
},
{
"[%]{'ElementA'}[%]{'ElementB'}[%]{'Content'}" => '%contentB%',
"[%]{'ElementA'}[%]{'ElementC'}[%]{'Content'}" => '%contentB%',
},
{
# use array reference if different content with same key was searched
"[%]{'ElementA'}[%]{'ElementB'}[%]{'Content'}" => ['%contentC%', '%contentD%', '%contentE%'],
"[%]{'ElementA'}[%]{'ElementC'}[%]{'Content'}" => ['%contentC%', '%contentD%', '%contentE%'],
},
],
PreviousVersionSearch => 1, # (optional) default 0 (0|1)
OrderBy => [ 'ConfigItemID', 'Number' ], # (optional)
# default: [ 'ConfigItemID' ]
# (ConfigItemID, Number, Name, ClassID, DeplStateID, InciStateID,
# CreateTime, CreateBy, ChangeTime, ChangeBy)
# Additional information for OrderBy:
# The OrderByDirection can be specified for each OrderBy attribute.
# The pairing is made by the array indices.
OrderByDirection => [ 'Down', 'Up' ], # (optional)
# default: [ 'Down' ]
# (Down | Up)
Limit => 122, # (optional)
UsingWildcards => 0, # (optional) default 1
);
=cut
sub ConfigItemSearchExtended {
my ( $Self, %Param ) = @_;
# set limit
my $Limit = $Param{Limit};
$Param{Limit} = undef;
# config item search is required if one of these params is given
my @ConfigItemSearchParams = (
'ConfigItemCreateTimeNewerDate',
'ConfigItemCreateTimeOlderDate',
'ConfigItemChangeTimeNewerDate',
'ConfigItemChangeTimeOlderDate',
);
# check, if config item search is required
my %RequiredSearch;
CONFIGITEMPARAM:
for my $ConfigItemParam (@ConfigItemSearchParams) {
next CONFIGITEMPARAM if !$Param{$ConfigItemParam};
$RequiredSearch{ConfigItem} = 1;
last CONFIGITEMPARAM;
}
# special handling for config item number
# number 0 is allowed but not the empty string
if ( defined $Param{Number} && $Param{Number} ne '' ) {
$RequiredSearch{ConfigItem} = 1;
}
# version search is required if Name, What or PreviousVersionSearch is given
if (
IsStringWithData( $Param{Name} )
|| IsArrayRefWithData( $Param{What} )
|| $Param{PreviousVersionSearch}
)
{
$RequiredSearch{Version} = 1;
}
# version search is also required if sorting by name (fix for bug #7072)
ORDERBY:
for my $OrderBy ( @{ $Param{OrderBy} } ) {
if ( $OrderBy eq 'Name' ) {
$RequiredSearch{Version} = 1;
last ORDERBY;
}
}
# xml version search is required if What is given
if ( IsArrayRefWithData( $Param{What} ) ) {
$RequiredSearch{XMLVersion} = 1;
}
# use config item search as fallback
if ( !%RequiredSearch ) {
$RequiredSearch{ConfigItem} = 1;
}
# start config item search
my %ConfigItemLists;
if ( $RequiredSearch{ConfigItem} ) {
# search config items
$ConfigItemLists{ConfigItem} = $Self->ConfigItemSearch(%Param);
return if !$ConfigItemLists{ConfigItem};
return if ref $ConfigItemLists{ConfigItem} ne 'ARRAY';
return [] if !@{ $ConfigItemLists{ConfigItem} };
}
# start version search
if ( $RequiredSearch{Version} ) {
# search versions
$ConfigItemLists{Version} = $Self->VersionSearch(%Param);
return if !$ConfigItemLists{Version};
return if ref $ConfigItemLists{Version} ne 'ARRAY';
return [] if !@{ $ConfigItemLists{Version} };
}
# start xml version search
if ( $RequiredSearch{XMLVersion} ) {
# search xml versions
my $XMLVersionList = $Self->_XMLVersionSearch(%Param);
return if !$XMLVersionList;
return if ref $XMLVersionList ne 'HASH';
return [] if !%{$XMLVersionList};
# get config item ids
my %ConfigItemListTmp;
VERSIONID:
for my $VersionID ( sort keys %{$XMLVersionList} ) {
my $ConfigItemID = $Self->VersionConfigItemIDGet(
VersionID => $VersionID,
);
next VERSIONID if !$ConfigItemID;
$ConfigItemListTmp{$ConfigItemID} = 1;
}
# add ids to config item list
$ConfigItemLists{XMLVersion} = \%ConfigItemListTmp;
}
# create the result list
my @ResultList;
if ( $RequiredSearch{ConfigItem} && $RequiredSearch{Version} ) {
# build a lookup hash of all found configitem ids in $ConfigItemLists{ConfigItem}
my %ConfigItemSeen = map { $_ => 1 } @{ $ConfigItemLists{ConfigItem} };
# check all config item ids, we need to keep the sorting
CONFIGITEMID:
for my $ConfigItemID ( @{ $ConfigItemLists{Version} } ) {
next CONFIGITEMID if !$ConfigItemSeen{$ConfigItemID};
push @ResultList, $ConfigItemID;
}
}
elsif ( $RequiredSearch{ConfigItem} ) {
@ResultList = @{ $ConfigItemLists{ConfigItem} };
}
elsif ( $RequiredSearch{Version} ) {
@ResultList = @{ $ConfigItemLists{Version} };
}
# consider the XML result
if ( $RequiredSearch{XMLVersion} ) {
@ResultList = grep { $ConfigItemLists{XMLVersion}->{$_} } @ResultList;
}
# consider limit
if ( $Limit && $Limit < scalar @ResultList ) {
# extract the limited ids
$Limit--;
@ResultList = @ResultList[ 0 .. $Limit ];
}
return \@ResultList;
}
=head2 ConfigItemSearch()
return a config item list as an array reference
my $ConfigItemIDs = $ConfigItemObject->ConfigItemSearch(
Number => 'The ConfigItem Number', # (optional)
ClassIDs => [9, 8, 7, 6], # (optional)
DeplStateIDs => [1, 2, 3, 4], # (optional)
InciStateIDs => [1, 2, 3, 4], # (optional)
CreateBy => [1, 2, 3], # (optional)
ChangeBy => [3, 2, 1], # (optional)
# config items with created time after ...
ConfigItemCreateTimeNewerDate => '2006-01-09 00:00:01', # (optional)
# config items with created time before then ....
ConfigItemCreateTimeOlderDate => '2006-01-19 23:59:59', # (optional)
# config items with changed time after ...
ConfigItemChangeTimeNewerDate => '2006-01-09 00:00:01', # (optional)
# config items with changed time before then ....
ConfigItemChangeTimeOlderDate => '2006-01-19 23:59:59', # (optional)
OrderBy => [ 'ConfigItemID', 'Number' ], # (optional)
# default: [ 'ConfigItemID' ]
# (ConfigItemID, Number, ClassID, DeplStateID, InciStateID,
# CreateTime, CreateBy, ChangeTime, ChangeBy)
# Additional information for OrderBy:
# The OrderByDirection can be specified for each OrderBy attribute.
# The pairing is made by the array indices.
OrderByDirection => [ 'Down', 'Up' ], # (optional)
# default: [ 'Down' ]
# (Down | Up)
Limit => 122, # (optional)
UsingWildcards => 0, # (optional) default 1
);
=cut
sub ConfigItemSearch {
my ( $Self, %Param ) = @_;
# verify that all passed array parameters contain an arrayref
ARGUMENT:
for my $Argument (
qw(
OrderBy
OrderByDirection
)
)
{
if ( !defined $Param{$Argument} ) {
$Param{$Argument} ||= [];
next ARGUMENT;
}
if ( ref $Param{$Argument} ne 'ARRAY' ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "$Argument must be an array reference!",
);
return;
}
}
# define order table
my %OrderByTable = (
ConfigItemID => 'id',
Number => 'configitem_number',
ClassID => 'class_id',
DeplStateID => 'cur_depl_state_id',
InciStateID => 'cur_inci_state_id',
CreateTime => 'create_time',
CreateBy => 'create_by',
ChangeTime => 'change_time',
ChangeBy => 'change_by',
);
# check if OrderBy contains only unique valid values
my %OrderBySeen;
ORDERBY:
for my $OrderBy ( @{ $Param{OrderBy} } ) {
next ORDERBY if $OrderBy eq 'Name';
if ( !$OrderBy || !$OrderByTable{$OrderBy} || $OrderBySeen{$OrderBy} ) {
# found an error
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "OrderBy contains invalid value '$OrderBy' "
. 'or the value is used more than once!',
);
return;
}
# remember the value to check if it appears more than once
$OrderBySeen{$OrderBy} = 1;
}
# check if OrderByDirection array contains only 'Up' or 'Down'
DIRECTION:
for my $Direction ( @{ $Param{OrderByDirection} } ) {
# only 'Up' or 'Down' allowed
next DIRECTION if $Direction eq 'Up';
next DIRECTION if $Direction eq 'Down';
# found an error
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "OrderByDirection can only contain 'Up' or 'Down'!",
);
return;
}
# set default values
if ( !defined $Param{UsingWildcards} ) {
$Param{UsingWildcards} = 1;
}
# get like escape string needed for some databases (e.g. oracle)
my $LikeEscapeString = $Kernel::OM->Get('Kernel::System::DB')->GetDatabaseFunction('LikeEscapeString');
# assemble the ORDER BY clause
my @SQLOrderBy;
my $Count = 0;
ORDERBY:
for my $OrderBy ( @{ $Param{OrderBy} } ) {
next ORDERBY if $OrderBy eq 'Name';
# set the default order direction
my $Direction = 'DESC';
# add the given order direction
if ( $Param{OrderByDirection}->[$Count] ) {
if ( $Param{OrderByDirection}->[$Count] eq 'Up' ) {
$Direction = 'ASC';
}
elsif ( $Param{OrderByDirection}->[$Count] eq 'Down' ) {
$Direction = 'DESC';
}
}
# add SQL
push @SQLOrderBy, "$OrderByTable{$OrderBy} $Direction";
}
continue {
$Count++;
}
# if there is a possibility that the ordering is not determined
# we add an ascending ordering by id
if ( !grep { $_ eq 'ConfigItemID' } ( @{ $Param{OrderBy} } ) ) {
push @SQLOrderBy, "$OrderByTable{ConfigItemID} ASC";
}
# add number to sql where array
my @SQLWhere;
if ( defined $Param{Number} && $Param{Number} ne '' && ref $Param{Number} ne 'ARRAY' ) {
# quote
$Param{Number} = $Kernel::OM->Get('Kernel::System::DB')->Quote( $Param{Number} );
if ( $Param{UsingWildcards} ) {
# prepare like string
$Self->_PrepareLikeString( \$Param{Number} );
push @SQLWhere,
"LOWER(configitem_number) LIKE LOWER('$Param{Number}') $LikeEscapeString";
}
else {
push @SQLWhere, "LOWER(configitem_number) = LOWER('$Param{Number}')";
}
}
elsif ( defined $Param{Number} && $Param{Number} ne '' && ref $Param{Number} eq 'ARRAY' ) {
# Create string.
my $InString = join q{, }, @{ $Param{Number} };
push @SQLWhere, "LOWER(configitem_number) IN ($InString)";
}
# set array params
my %ArrayParams = (
ConfigItemIDs => 'id',
ClassIDs => 'class_id',
DeplStateIDs => 'cur_depl_state_id',
InciStateIDs => 'cur_inci_state_id',
CreateBy => 'create_by',
ChangeBy => 'change_by',
);
ARRAYPARAM:
for my $ArrayParam ( sort keys %ArrayParams ) {
next ARRAYPARAM if !$Param{$ArrayParam};
if ( ref $Param{$ArrayParam} ne 'ARRAY' ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "$ArrayParam must be an array reference!",
);
return;
}
next ARRAYPARAM if !@{ $Param{$ArrayParam} };
# quote as integer
for my $OneParam ( @{ $Param{$ArrayParam} } ) {
$OneParam = $Kernel::OM->Get('Kernel::System::DB')->Quote( $OneParam, 'Integer' );
}
# create string
my $InString = join q{, }, @{ $Param{$ArrayParam} };
next ARRAYPARAM if !$InString;
push @SQLWhere, "$ArrayParams{ $ArrayParam } IN ($InString)";
}
# set time params
my %TimeParams = (
ConfigItemCreateTimeNewerDate => 'create_time >=',
ConfigItemCreateTimeOlderDate => 'create_time <=',
ConfigItemChangeTimeNewerDate => 'change_time >=',
ConfigItemChangeTimeOlderDate => 'change_time <=',
);
TIMEPARAM:
for my $TimeParam ( sort keys %TimeParams ) {
next TIMEPARAM if !$Param{$TimeParam};
if ( $Param{$TimeParam} !~ m{ \A \d\d\d\d-\d\d-\d\d \s \d\d:\d\d:\d\d \z }xms ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Invalid date format found!",
);
return;
}
# quote
$Param{$TimeParam} = $Kernel::OM->Get('Kernel::System::DB')->Quote( $Param{$TimeParam} );
push @SQLWhere, "$TimeParams{ $TimeParam } '$Param{ $TimeParam }'";
}
# create where string
my $WhereString = @SQLWhere ? ' WHERE ' . join q{ AND }, @SQLWhere : '';
# set limit
if ( $Param{Limit} ) {
$Param{Limit} = $Kernel::OM->Get('Kernel::System::DB')->Quote( $Param{Limit}, 'Integer' );
}
my $SQL = "SELECT id FROM configitem $WhereString ";
# add the ORDER BY clause
if (@SQLOrderBy) {
$SQL .= 'ORDER BY ';
$SQL .= join ', ', @SQLOrderBy;
$SQL .= ' ';
}
# ask database
$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => $SQL,
Limit => $Param{Limit},
);
# fetch the result
my @ConfigItemList;
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
push @ConfigItemList, $Row[0];
}
return \@ConfigItemList;
}
=head2 ConfigItemLookup()
This method does a lookup for a config-item. If a config-item id is given,
it returns the number of the config-item. If a config-item number is given,
the appropriate id is returned.
my $Number = $ConfigItemObject->ConfigItemLookup(
ConfigItemID => 1234,
);
my $ID = $ConfigItemObject->ConfigItemLookup(
ConfigItemNumber => 1000001,
);
=cut
sub ConfigItemLookup {
my ( $Self, %Param ) = @_;
my ($Key) = grep { $Param{$_} } qw(ConfigItemID ConfigItemNumber);
# check for needed stuff
if ( !$Key ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need ConfigItemID or ConfigItemNumber!',
);
return;
}
# if result is cached return that result
return $Self->{Cache}->{ConfigItemLookup}->{$Key}->{ $Param{$Key} }
if $Self->{Cache}->{ConfigItemLookup}->{$Key}->{ $Param{$Key} };
# set the appropriate SQL statement
my $SQL = 'SELECT configitem_number FROM configitem WHERE id = ?';
if ( $Key eq 'ConfigItemNumber' ) {
$SQL = 'SELECT id FROM configitem WHERE configitem_number = ?';
}
# fetch the requested value
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
SQL => $SQL,
Bind => [ \$Param{$Key} ],
Limit => 1,
);
my $Value;
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
$Value = $Row[0];
}
$Self->{Cache}->{ConfigItemLookup}->{$Key}->{ $Param{$Key} } = $Value;
return $Value;
}
=head2 UniqueNameCheck()
This method checks all already existing config items, whether the given name does already exist
within the same config item class or among all classes, depending on the SysConfig value of
UniqueCIName::UniquenessCheckScope (Class or Global).
This method requires 3 parameters: ConfigItemID, Name and Class
"ConfigItemID" is the ID of the ConfigItem, which is to be checked for uniqueness
"Name" is the config item name to be checked for uniqueness
"ClassID" is the ID of the config item's class
All parameters are mandatory.
my $DuplicateNames = $ConfigItemObject->UniqueNameCheck(
ConfigItemID => '73'
Name => 'PC#005',
ClassID => '32',
);
The given name is not unique
my $NameDuplicates = [ 5, 35, 48, ]; # IDs of ConfigItems with the same name
The given name is unique
my $NameDuplicates = [];
=cut
sub UniqueNameCheck {
my ( $Self, %Param ) = @_;
# check for needed stuff
for my $Needed (qw(ConfigItemID Name ClassID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Missing parameter $Needed!",
);
return;
}
}
# check ConfigItemID param for valid format
if (
!IsInteger( $Param{ConfigItemID} )
&& ( IsStringWithData( $Param{ConfigItemID} ) && $Param{ConfigItemID} ne 'NEW' )
)
{
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "The ConfigItemID parameter needs to be an integer or 'NEW'",
);
return;
}
# check Name param for valid format
if ( !IsStringWithData( $Param{Name} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "The Name parameter needs to be a string!",
);
return;
}
# check ClassID param for valid format
if ( !IsInteger( $Param{ClassID} ) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "The ClassID parameter needs to be an integer",
);
return;
}
# get class list
my $ClassList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
Class => 'ITSM::ConfigItem::Class',
);
# check class list for validity
if ( !IsHashRefWithData($ClassList) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Unable to retrieve a valid class list!",
);
return;
}
# get the class name from the class list
my $Class = $ClassList->{ $Param{ClassID} };
# check class for validity
if ( !IsStringWithData($Class) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Unable to determine a config item class using the given ClassID!",
);
return;
}
elsif ( $Kernel::OM->Get('Kernel::Config')->{Debug} > 0 ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => "Resolved ClassID $Param{ClassID} to class $Class",
);
}
# get the uniqueness scope from SysConfig
my $Scope = $Kernel::OM->Get('Kernel::Config')->Get('UniqueCIName::UniquenessCheckScope');
# check scope for validity
if ( !IsStringWithData($Scope) ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "The configuration of UniqueCIName::UniquenessCheckScope is invalid!",
);
return;
}
if ( $Scope ne 'global' && $Scope ne 'class' ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "UniqueCIName::UniquenessCheckScope is $Scope, but must be either "
. "'global' or 'class'!",
);
return;
}
if ( $Kernel::OM->Get('Kernel::Config')->{Debug} > 0 ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'debug',
Message => "The scope for checking the uniqueness is $Scope",
);
}
my %SearchCriteria;
# add the config item class to the search criteria if the uniqueness scope is not global
if ( $Scope ne 'global' ) {
$SearchCriteria{ClassIDs} = [ $Param{ClassID} ];
}
$SearchCriteria{Name} = $Param{Name};
# search for a config item matching the given name
my $ConfigItem = $Self->ConfigItemSearchExtended(%SearchCriteria);
# remove the provided ConfigItemID from the results, otherwise the duplicate check would fail
# because the ConfigItem itself is found as duplicate
my @Duplicates = map {$_} grep { $_ ne $Param{ConfigItemID} } @{$ConfigItem};
# if a config item was found, the given name is not unique
# if no config item was found, the given name is unique
# return the result of the config item search for duplicates
return \@Duplicates;
}
=head2 CurInciStateRecalc()
recalculates the current incident state of this config item and all linked config items
my $Success = $ConfigItemObject->CurInciStateRecalc(
ConfigItemID => 123,
NewConfigItemIncidentState => $NewConfigItemIncidentState, # optional, incident states of already checked CIs
ScannedConfigItemIDs => $ScannedConfigItemIDs, # optional, IDs of already checked CIs
);
=cut
sub CurInciStateRecalc {
my ( $Self, %Param ) = @_;
# check needed stuff
if ( !$Param{ConfigItemID} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => 'Need ConfigItemID!',
);
return;
}
# get incident link types and directions from config
my $IncidentLinkTypeDirection = $Kernel::OM->Get('Kernel::Config')->Get('ITSM::Core::IncidentLinkTypeDirection');
# to store the new incident state for CIs
# calculated from all incident link types
# Incorporate data from previous run(s) and remember known data.
$Param{NewConfigItemIncidentState} //= {};
my $KnownNewConfigItemIncidentState = Storable::dclone( $Param{NewConfigItemIncidentState} );
# to store the relation between services and linked CIs
my %ServiceCIRelation;
# remember the scanned config items
# Incorporate data from previous run(s) and remember known data.
$Param{ScannedConfigItemIDs} //= {};
my $KnownScannedConfigItemIDs = Storable::dclone( $Param{ScannedConfigItemIDs} );
# find all config items with an incident state
$Self->_FindInciConfigItems(
ConfigItemID => $Param{ConfigItemID},
IncidentLinkTypeDirection => $IncidentLinkTypeDirection,
ScannedConfigItemIDs => $Param{ScannedConfigItemIDs},
);
# calculate the new CI incident state for each configured linktype
LINKTYPE:
for my $LinkType ( sort keys %{$IncidentLinkTypeDirection} ) {
# get the direction
my $LinkDirection = $IncidentLinkTypeDirection->{$LinkType};
# investigate all config items with a warning state
CONFIGITEMID:
for my $ConfigItemID ( sort keys %{ $Param{ScannedConfigItemIDs} } ) {
# Skip config items known from previous execution(s).
if (
IsStringWithData( $KnownScannedConfigItemIDs->{$ConfigItemID}->{Type} )
&& $KnownScannedConfigItemIDs->{$ConfigItemID}->{Type} eq
$Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type}
)
{
next CONFIGITEMID;
}
# investigate only config items with an incident state
next CONFIGITEMID if $Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type} ne 'incident';
$Self->_FindWarnConfigItems(
ConfigItemID => $ConfigItemID,
LinkType => $LinkType,
Direction => $LinkDirection,
NumberOfLinkTypes => scalar keys %{$IncidentLinkTypeDirection},
ScannedConfigItemIDs => $Param{ScannedConfigItemIDs},
);
}
CONFIGITEMID:
for my $ConfigItemID ( sort keys %{ $Param{ScannedConfigItemIDs} } ) {
# Skip config items known from previous execution(s).
if (
IsStringWithData( $KnownScannedConfigItemIDs->{$ConfigItemID}->{Type} )
&& $KnownScannedConfigItemIDs->{$ConfigItemID}->{Type} eq
$Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type}
)
{
next CONFIGITEMID;
}
# extract incident state type
my $InciStateType = $Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type};
# find all linked services of this CI
my %LinkedServiceIDs = $Kernel::OM->Get('Kernel::System::LinkObject')->LinkKeyList(
Object1 => 'ITSMConfigItem',
Key1 => $ConfigItemID,
Object2 => 'Service',
State => 'Valid',
Type => $LinkType,
Direction => $LinkDirection,
UserID => 1,
);
SERVICEID:
for my $ServiceID ( sort keys %LinkedServiceIDs ) {
# remember the CIs that are linked with this service
push @{ $ServiceCIRelation{$ServiceID} }, $ConfigItemID;
}
next CONFIGITEMID if $InciStateType eq 'incident';
$Param{NewConfigItemIncidentState}->{$ConfigItemID} = $InciStateType;
}
}
# get the incident state list of warnings
my $WarnStateList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
Class => 'ITSM::Core::IncidentState',
Preferences => {
Functionality => 'warning',
},
);
if ( !defined $WarnStateList ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "ITSM::Core::IncidentState Warning cannot be invalid.",
);
}
my %ReverseWarnStateList = reverse %{$WarnStateList};
my @SortedWarnList = sort keys %ReverseWarnStateList;
my $WarningStateID = $ReverseWarnStateList{Warning} || $ReverseWarnStateList{ $SortedWarnList[0] };
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
# set the new current incident state for CIs
CONFIGITEMID:
for my $ConfigItemID ( sort keys %{ $Param{NewConfigItemIncidentState} } ) {
# Skip config items known from previous execution(s).
if (
IsStringWithData( $KnownNewConfigItemIncidentState->{$ConfigItemID} )
&& $KnownNewConfigItemIncidentState->{$ConfigItemID} eq $Param{NewConfigItemIncidentState}->{$ConfigItemID}
)
{
next CONFIGITEMID;
}
# get new incident state type (can only be 'operational' or 'warning')
my $InciStateType = $Param{NewConfigItemIncidentState}->{$ConfigItemID};
# get last version
my $LastVersion = $Self->VersionGet(
ConfigItemID => $ConfigItemID,
XMLDataGet => 0,
);
my $CurInciStateID;
if ( $InciStateType eq 'warning' ) {
# check the current incident state type is in 'incident'
# then we do not want to change it to warning
next CONFIGITEMID if $LastVersion->{InciStateType} eq 'incident';
$CurInciStateID = $WarningStateID;
}
elsif ( $InciStateType eq 'operational' ) {
$CurInciStateID = $LastVersion->{InciStateID};
}
# No update necessary if incident state id of version and config item match.
next CONFIGITEMID if $LastVersion->{CurInciStateID} eq $CurInciStateID;
# update current incident state
$Kernel::OM->Get('Kernel::System::DB')->Do(
SQL => 'UPDATE configitem SET cur_inci_state_id = ? WHERE id = ?',
Bind => [ \$CurInciStateID, \$ConfigItemID ],
);
# delete the cache
my $CacheKey = 'ConfigItemGet::ConfigItemID::' . $ConfigItemID;
$CacheObject->Delete(
Type => $Self->{CacheType},
Key => $CacheKey,
);
# delete affected caches for ConfigItemID
$CacheKey = 'VersionGet::ConfigItemID::' . $ConfigItemID . '::XMLData::';
for my $XMLData (qw(0 1)) {
$CacheObject->Delete(
Type => $Self->{CacheType},
Key => $CacheKey . $XMLData,
);
}
$CacheObject->Delete(
Type => $Self->{CacheType},
Key => 'VersionNameGet::ConfigItemID::' . $ConfigItemID,
);
# delete affected caches for last version
my $VersionList = $Self->VersionList(
ConfigItemID => $ConfigItemID,
);
my $VersionID = $VersionList->[-1];
$CacheKey = 'VersionGet::VersionID::' . $VersionID . '::XMLData::';
for my $XMLData (qw(0 1)) {
$CacheObject->Delete(
Type => $Self->{CacheType},
Key => $CacheKey . $XMLData,
);
}
$CacheObject->Delete(
Type => $Self->{CacheType},
Key => 'VersionNameGet::VersionID::' . $VersionID,
);
}
# set the current incident state type for each service (influenced by linked CIs)
SERVICEID:
for my $ServiceID ( sort keys %ServiceCIRelation ) {
# set default incident state type
my $CurInciStateTypeFromCIs = 'operational';
# get the unique config item ids which are direcly linked to this service
my %UniqueConfigItemIDs = map { $_ => 1 } @{ $ServiceCIRelation{$ServiceID} };
# investigate the current incident state of each config item
CONFIGITEMID:
for my $ConfigItemID ( sort keys %UniqueConfigItemIDs ) {
# get config item data
my $ConfigItemData = $Self->ConfigItemGet(
ConfigItemID => $ConfigItemID,
Cache => 0,
);
next CONFIGITEMID if $ConfigItemData->{CurDeplStateType} ne 'productive';
next CONFIGITEMID if $ConfigItemData->{CurInciStateType} eq 'operational';
# check if service must be set to 'warning'
if ( $ConfigItemData->{CurInciStateType} eq 'warning' ) {
$CurInciStateTypeFromCIs = 'warning';
next CONFIGITEMID;
}
# check if service must be set to 'incident'
if ( $ConfigItemData->{CurInciStateType} eq 'incident' ) {
$CurInciStateTypeFromCIs = 'incident';
last CONFIGITEMID;
}
}
# update the current incident state type from CIs of the service
$Kernel::OM->Get('Kernel::System::Service')->ServicePreferencesSet(
ServiceID => $ServiceID,
Key => 'CurInciStateTypeFromCIs',
Value => $CurInciStateTypeFromCIs,
UserID => 1,
);
}
return 1;
}
=head1 INTERNAL INTERFACE
=head2 _FindInciConfigItems()
find all config items with an incident state
$ConfigItemObject->_FindInciConfigItems(
ConfigItemID => $ConfigItemID,
IncidentLinkTypeDirection => $IncidentLinkTypeDirection,
ScannedConfigItemIDs => \%ScannedConfigItemIDs,
);
=cut
sub _FindInciConfigItems {
my ( $Self, %Param ) = @_;
# check needed stuff
return if !$Param{ConfigItemID};
# ignore already scanned ids (infinite loop protection)
return if $Param{ScannedConfigItemIDs}->{ $Param{ConfigItemID} };
$Param{ScannedConfigItemIDs}->{ $Param{ConfigItemID} }->{Type} = 'operational';
# add own config item id to list of linked config items
my %ConfigItemIDs = (
$Param{ConfigItemID} => 1,
);
LINKTYPE:
for my $LinkType ( sort keys %{ $Param{IncidentLinkTypeDirection} } ) {
# find all linked config items (childs)
my %LinkedConfigItemIDs = $Kernel::OM->Get('Kernel::System::LinkObject')->LinkKeyList(
Object1 => 'ITSMConfigItem',
Key1 => $Param{ConfigItemID},
Object2 => 'ITSMConfigItem',
State => 'Valid',
Type => $LinkType,
# Direction must ALWAYS be 'Both' here as we need to include
# all linked CIs that could influence this one!
Direction => 'Both',
UserID => 1,
);
# remember the config item ids
%ConfigItemIDs = ( %ConfigItemIDs, %LinkedConfigItemIDs );
}
CONFIGITEMID:
for my $ConfigItemID ( sort keys %ConfigItemIDs ) {
# get config item data
my $ConfigItem = $Self->ConfigItemGet(
ConfigItemID => $ConfigItemID,
Cache => 0,
);
# set incident state
if ( $ConfigItem->{CurInciStateType} eq 'incident' ) {
$Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type} = 'incident';
next CONFIGITEMID;
}
# start recursion
$Self->_FindInciConfigItems(
ConfigItemID => $ConfigItemID,
IncidentLinkTypeDirection => $Param{IncidentLinkTypeDirection},
ScannedConfigItemIDs => $Param{ScannedConfigItemIDs},
);
}
return 1;
}
=head2 _FindWarnConfigItems()
find all config items with a warning
$ConfigItemObject->_FindWarnConfigItems(
ConfigItemID => $ConfigItemID,
LinkType => $LinkType,
Direction => $LinkDirection,
NumberOfLinkTypes => 2,
ScannedConfigItemIDs => $ScannedConfigItemIDs,
);
=cut
sub _FindWarnConfigItems {
my ( $Self, %Param ) = @_;
# check needed stuff
return if !$Param{ConfigItemID};
my $IncidentCount = 0;
for my $ConfigItemID ( sort keys %{ $Param{ScannedConfigItemIDs} } ) {
if (
$Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type}
&& $Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type} eq 'incident'
)
{
$IncidentCount++;
}
}
# ignore already scanned ids (infinite loop protection)
# it is ok that a config item is investigated as many times as there are configured link types * number of incident config iteems
if (
$Param{ScannedConfigItemIDs}->{ $Param{ConfigItemID} }->{FindWarn}
&& $Param{ScannedConfigItemIDs}->{ $Param{ConfigItemID} }->{FindWarn}
>= ( $Param{NumberOfLinkTypes} * $IncidentCount )
)
{
return;
}
# increase the visit counter
$Param{ScannedConfigItemIDs}->{ $Param{ConfigItemID} }->{FindWarn}++;
# find all linked config items
my %LinkedConfigItemIDs = $Kernel::OM->Get('Kernel::System::LinkObject')->LinkKeyList(
Object1 => 'ITSMConfigItem',
Key1 => $Param{ConfigItemID},
Object2 => 'ITSMConfigItem',
State => 'Valid',
Type => $Param{LinkType},
Direction => $Param{Direction},
UserID => 1,
);
CONFIGITEMID:
for my $ConfigItemID ( sort keys %LinkedConfigItemIDs ) {
# start recursion
$Self->_FindWarnConfigItems(
ConfigItemID => $ConfigItemID,
LinkType => $Param{LinkType},
Direction => $Param{Direction},
NumberOfLinkTypes => $Param{NumberOfLinkTypes},
ScannedConfigItemIDs => $Param{ScannedConfigItemIDs},
);
next CONFIGITEMID
if $Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type}
&& $Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type} eq 'incident';
# set warning state
$Param{ScannedConfigItemIDs}->{$ConfigItemID}->{Type} = 'warning';
}
return 1;
}
=head2 _PrepareLikeString()
internal function to prepare like strings
$ConfigItemObject->_PrepareLikeString( $StringRef );
=cut
sub _PrepareLikeString {
my ( $Self, $Value ) = @_;
return if !$Value;
return if ref $Value ne 'SCALAR';
# Quote
${$Value} = $Kernel::OM->Get('Kernel::System::DB')->Quote( ${$Value}, 'Like' );
# replace * with %
${$Value} =~ s{ \*+ }{%}xmsg;
return;
}
1;
=head1 ITSM Config Item events:
ConfigItemCreate, VersionCreate, DeploymentStateUpdate, IncidentStateUpdate,
ConfigItemDelete, LinkAdd, LinkDelete, DefinitionUpdate, NameUpdate, ValueUpdate
DefinitionCreate, VersionDelete
=cut
=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