3538 lines
110 KiB
Perl
3538 lines
110 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::ITSMChange;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Kernel::System::EventHandler;
|
|
use Kernel::System::VariableCheck qw(:all);
|
|
|
|
use vars qw(@ISA);
|
|
|
|
our @ObjectDependencies = (
|
|
'Kernel::Config',
|
|
'Kernel::System::Cache',
|
|
'Kernel::System::CustomerUser',
|
|
'Kernel::System::DB',
|
|
'Kernel::System::DynamicField',
|
|
'Kernel::System::DynamicField::Backend',
|
|
'Kernel::System::Encode',
|
|
'Kernel::System::GeneralCatalog',
|
|
'Kernel::System::HTMLUtils',
|
|
'Kernel::System::ITSMChange::ITSMChangeCIPAllocate',
|
|
'Kernel::System::ITSMChange::ITSMCondition',
|
|
'Kernel::System::ITSMChange::ITSMStateMachine',
|
|
'Kernel::System::ITSMChange::ITSMWorkOrder',
|
|
'Kernel::System::LinkObject',
|
|
'Kernel::System::Log',
|
|
'Kernel::System::Main',
|
|
'Kernel::System::User',
|
|
'Kernel::System::Valid',
|
|
'Kernel::System::VirtualFS',
|
|
);
|
|
|
|
=head1 NAME
|
|
|
|
Kernel::System::ITSMChange - change lib
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
All functions for changes in ITSMChangeManagement.
|
|
|
|
=head1 PUBLIC INTERFACE
|
|
|
|
=head2 new()
|
|
|
|
create an object
|
|
|
|
use Kernel::System::ObjectManager;
|
|
local $Kernel::OM = Kernel::System::ObjectManager->new();
|
|
my $ChangeObject = $Kernel::OM->Get('Kernel::System::ITSMChange');
|
|
|
|
=cut
|
|
|
|
sub new {
|
|
my ( $Type, %Param ) = @_;
|
|
|
|
# allocate new hash for object
|
|
my $Self = {};
|
|
bless( $Self, $Type );
|
|
|
|
# set the debug flag
|
|
$Self->{Debug} = $Param{Debug} || 0;
|
|
|
|
# get the cache type and TTL (in seconds)
|
|
$Self->{CacheType} = 'ITSMChangeManagement';
|
|
$Self->{CacheTTL} = $Kernel::OM->Get('Kernel::Config')->Get('ITSMChange::CacheTTL') * 60;
|
|
|
|
@ISA = (
|
|
'Kernel::System::EventHandler',
|
|
);
|
|
|
|
# init of event handler
|
|
$Self->EventHandlerInit(
|
|
Config => 'ITSMChange::EventModule',
|
|
);
|
|
|
|
# load change number generator
|
|
my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('ITSMChange::NumberGenerator')
|
|
|| 'Kernel::System::ITSMChange::Number::DateChecksum';
|
|
if ( !$Kernel::OM->Get('Kernel::System::Main')->RequireBaseClass($GeneratorModule) ) {
|
|
die "Can't load change number generator backend module $GeneratorModule! $@";
|
|
}
|
|
|
|
# get database type
|
|
$Self->{DBType} = $Kernel::OM->Get('Kernel::System::DB')->{'DB::Type'} || '';
|
|
$Self->{DBType} = lc $Self->{DBType};
|
|
|
|
return $Self;
|
|
}
|
|
|
|
=head2 ChangeAdd()
|
|
|
|
Add a new change. The UserID is the only required parameter.
|
|
Internally first a minimal change is created, then ChangeUpdate() is called.
|
|
|
|
my $ChangeID = $ChangeObject->ChangeAdd(
|
|
UserID => 1,
|
|
);
|
|
|
|
or
|
|
|
|
my $ChangeID = $ChangeObject->ChangeAdd(
|
|
ChangeTitle => 'Replacement of mail server', # (optional)
|
|
Description => 'New mail server is faster', # (optional)
|
|
Justification => 'Old mail server too slow', # (optional)
|
|
ChangeStateID => 4, # (optional) or ChangeState => 'accepted'
|
|
ChangeState => 'accepted', # (optional) or ChangeStateID => 4
|
|
ChangeManagerID => 5, # (optional)
|
|
ChangeBuilderID => 6, # (optional)
|
|
CategoryID => 7, # (optional) or Category => '3 normal'
|
|
Category => '3 normal', # (optional) or CategoryID => 4
|
|
ImpactID => 8, # (optional) or Impact => '4 high'
|
|
Impact => '4 high', # (optional) or ImpactID => 5
|
|
PriorityID => 9, # (optional) or Priority => '5 very high'
|
|
Priority => '5 very high', # (optional) or PriorityID => 6
|
|
CABAgents => [ 1, 2, 4 ], # UserIDs # (optional)
|
|
CABCustomers => [ 'tt', 'mm' ], # CustomerUserIDs # (optional)
|
|
RequestedTime => '2006-01-19 23:59:59', # (optional)
|
|
DynamicField_X => 'Sun', # (optional)
|
|
DynamicField_Y => 'Earth', # (optional)
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeAdd {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need UserID!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get a plain text version of arguments which might contain HTML markup
|
|
ARGUMENT:
|
|
for my $Argument (qw(Description Justification)) {
|
|
|
|
next ARGUMENT if !exists $Param{$Argument};
|
|
|
|
$Param{"${Argument}Plain"} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii(
|
|
String => $Param{$Argument},
|
|
);
|
|
|
|
# Even when passed a plain ASCII string,
|
|
# ToAscii() can return a non-utf8 string with chars in the extended range.
|
|
# Upgrade to utf-8 in order to comply to the OTRS-convention.
|
|
utf8::upgrade( $Param{"${Argument}Plain"} );
|
|
}
|
|
|
|
# check the parameters
|
|
return if !$Self->_CheckChangeParams(%Param);
|
|
|
|
# trigger ChangeAddPre-Event
|
|
$Self->EventHandler(
|
|
Event => 'ChangeAddPre',
|
|
Data => {
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# create a new change number
|
|
my $ChangeNumber = $Self->ChangeCreateNumber();
|
|
|
|
# get initial change state id
|
|
my $ChangeStateID = delete $Param{ChangeStateID};
|
|
if ( !$ChangeStateID ) {
|
|
my $NextStateIDs = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMStateMachine')->StateTransitionGet(
|
|
StateID => 0,
|
|
Class => 'ITSM::ChangeManagement::Change::State',
|
|
);
|
|
$ChangeStateID = $NextStateIDs->[0];
|
|
}
|
|
|
|
# get default Category if not defined
|
|
my $CategoryID = delete $Param{CategoryID};
|
|
if ( !$CategoryID ) {
|
|
my $DefaultCategory = $Kernel::OM->Get('Kernel::Config')->Get('ITSMChange::Category::Default');
|
|
$CategoryID = $Self->ChangeCIPLookup(
|
|
CIP => $DefaultCategory,
|
|
Type => 'Category',
|
|
);
|
|
}
|
|
|
|
# get default Impact if not defined
|
|
my $ImpactID = delete $Param{ImpactID};
|
|
if ( !$ImpactID ) {
|
|
my $DefaultImpact = $Kernel::OM->Get('Kernel::Config')->Get('ITSMChange::Impact::Default');
|
|
$ImpactID = $Self->ChangeCIPLookup(
|
|
CIP => $DefaultImpact,
|
|
Type => 'Impact',
|
|
);
|
|
}
|
|
|
|
# get default Priority if not defined
|
|
my $PriorityID = delete $Param{PriorityID};
|
|
if ( !$PriorityID ) {
|
|
$PriorityID = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMChangeCIPAllocate')->PriorityAllocationGet(
|
|
CategoryID => $CategoryID,
|
|
ImpactID => $ImpactID,
|
|
);
|
|
}
|
|
|
|
# if no change builder id was given, take the user id
|
|
my $ChangeBuilderID = $Param{ChangeBuilderID} || $Param{UserID};
|
|
|
|
# add change to database
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'INSERT INTO change_item '
|
|
. '(change_number, change_state_id, change_builder_id, '
|
|
. 'category_id, impact_id, priority_id, '
|
|
. 'create_time, create_by, change_time, change_by) '
|
|
. 'VALUES (?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)',
|
|
Bind => [
|
|
\$ChangeNumber, \$ChangeStateID, \$ChangeBuilderID,
|
|
\$CategoryID, \$ImpactID, \$PriorityID,
|
|
\$Param{UserID}, \$Param{UserID},
|
|
],
|
|
);
|
|
|
|
# get change id
|
|
my $ChangeID = $Self->ChangeLookup(
|
|
ChangeNumber => $ChangeNumber,
|
|
);
|
|
|
|
return if !$ChangeID;
|
|
|
|
# delete cache
|
|
for my $Key (
|
|
'ChangeGet::ID::' . $ChangeID,
|
|
'ChangeList',
|
|
'ChangeLookup::ChangeID::' . $ChangeID,
|
|
'ChangeLookup::ChangeNumber::' . $ChangeNumber,
|
|
)
|
|
{
|
|
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
|
Type => $Self->{CacheType},
|
|
Key => $Key,
|
|
);
|
|
}
|
|
|
|
# trigger ChangeAddPost-Event
|
|
# (yes, we want do do this before the ChangeUpdate!)
|
|
# override the actually passed change state with the initial change state
|
|
$Self->EventHandler(
|
|
Event => 'ChangeAddPost',
|
|
Data => {
|
|
%Param,
|
|
ChangeID => $ChangeID,
|
|
CategoryID => $CategoryID,
|
|
ImpactID => $ImpactID,
|
|
PriorityID => $PriorityID,
|
|
ChangeStateID => $ChangeStateID,
|
|
ChangeNumber => $ChangeNumber,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# update change with remaining parameters
|
|
# the already handles params have been deleted from %Param
|
|
my $UpdateSuccess = $Self->ChangeUpdate(
|
|
%Param,
|
|
ChangeID => $ChangeID,
|
|
);
|
|
|
|
# check update error
|
|
if ( !$UpdateSuccess ) {
|
|
|
|
# delete change if it could not be updated
|
|
$Self->ChangeDelete(
|
|
ChangeID => $ChangeID,
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
return $ChangeID;
|
|
}
|
|
|
|
=head2 ChangeUpdate()
|
|
|
|
Update a change.
|
|
Leading and trailing whitespace is removed from C<ChangeTitle>.
|
|
Passing undefined values is generally not allowed.
|
|
An exception is the parameter C<RequestedTime>, where the undefined value
|
|
indicates that requested time of the change should be cleared.
|
|
|
|
my $Success = $ChangeObject->ChangeUpdate(
|
|
ChangeID => 123,
|
|
ChangeTitle => 'Replacement of slow mail server', # (optional)
|
|
Description => 'New mail server is faster', # (optional)
|
|
Justification => 'Old mail server too slow', # (optional)
|
|
ChangeStateID => 4, # (optional) or ChangeState => 'accepted'
|
|
ChangeState => 'accepted', # (optional) or ChangeStateID => 4
|
|
ChangeManagerID => 5, # (optional)
|
|
ChangeBuilderID => 6, # (optional)
|
|
CategoryID => 7, # (optional) or Category => '3 normal'
|
|
Category => '3 normal', # (optional) or CategoryID => 4
|
|
ImpactID => 8, # (optional) or Impact => '4 high'
|
|
Impact => '4 high', # (optional) or ImpactID => 5
|
|
PriorityID => 9, # (optional) or Priority => '5 very high'
|
|
Priority => '5 very high', # (optional) or PriorityID => 6
|
|
CABAgents => [ 1, 2, 4 ], # (optional) UserIDs
|
|
CABCustomers => [ 'tt', 'mm' ], # (optional) CustomerUserIDs
|
|
RequestedTime => '2006-01-19 23:59:59', # (optional) or 'undef', which clears the time
|
|
DynamicField_X => 'Sun', # (optional)
|
|
DynamicField_Y => 'Earth', # (optional)
|
|
BypassStateMachine => 1, # (optional) default 0, if 1 the state machine will be bypassed
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeUpdate {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Argument (qw(ChangeID UserID )) {
|
|
if ( !$Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Argument!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check that not both ChangeState and ChangeStateID are given
|
|
if ( $Param{ChangeState} && $Param{ChangeStateID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need either ChangeState OR ChangeStateID - not both!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# when the State is given, then look up the ID
|
|
if ( $Param{ChangeState} ) {
|
|
$Param{ChangeStateID} = $Self->ChangeStateLookup(
|
|
ChangeState => $Param{ChangeState},
|
|
);
|
|
}
|
|
|
|
# when CIP is given, then look up the ID,
|
|
# otherwise look up the names, this is needed for the notification rules
|
|
for my $Type (qw(Category Impact Priority)) {
|
|
if ( $Param{$Type} ) {
|
|
$Param{"${Type}ID"} = $Self->ChangeCIPLookup(
|
|
CIP => $Param{$Type},
|
|
Type => $Type,
|
|
);
|
|
}
|
|
elsif ( $Param{"${Type}ID"} ) {
|
|
$Param{$Type} = $Self->ChangeCIPLookup(
|
|
ID => $Param{"${Type}ID"},
|
|
Type => $Type,
|
|
);
|
|
}
|
|
}
|
|
|
|
# normalize the Title, when it is given
|
|
if ( $Param{ChangeTitle} && !ref $Param{ChangeTitle} ) {
|
|
|
|
# remove leading whitespace
|
|
$Param{ChangeTitle} =~ s{ \A \s+ }{}xms;
|
|
|
|
# remove trailing whitespace
|
|
$Param{ChangeTitle} =~ s{ \s+ \z }{}xms;
|
|
}
|
|
|
|
# get a plain text version of arguments which might contain HTML markup
|
|
ARGUMENT:
|
|
for my $Argument (qw(Description Justification)) {
|
|
|
|
next ARGUMENT if !exists $Param{$Argument};
|
|
|
|
$Param{"${Argument}Plain"} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii(
|
|
String => $Param{$Argument},
|
|
);
|
|
|
|
# Even when passed a plain ASCII string,
|
|
# ToAscii() can return a non-utf8 string with chars in the extended range.
|
|
# Upgrade to utf-8 in order to comply to the OTRS-convention.
|
|
utf8::upgrade( $Param{"${Argument}Plain"} );
|
|
}
|
|
|
|
# check the given parameters
|
|
return if !$Self->_CheckChangeParams(%Param);
|
|
|
|
# check sanity of the new state with the state machine
|
|
if ( $Param{ChangeStateID} ) {
|
|
|
|
# get change id
|
|
my $ChangeID = $Param{ChangeID};
|
|
|
|
# do not give ChangePossibleStatesGet() the ChangeID
|
|
# if the statemachine should be bypassed.
|
|
# ChangePossibleStatesGet() will then return all change states.
|
|
if ( $Param{BypassStateMachine} ) {
|
|
$ChangeID = undef;
|
|
}
|
|
|
|
# get the list of possible next states
|
|
my $StateList = $Self->ChangePossibleStatesGet(
|
|
ChangeID => $ChangeID,
|
|
UserID => $Param{UserID},
|
|
);
|
|
if ( !grep { $_->{Key} == $Param{ChangeStateID} } @{$StateList} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The state $Param{ChangeStateID} is not a possible next state!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# trigger ChangeUpdatePre-Event
|
|
$Self->EventHandler(
|
|
Event => 'ChangeUpdatePre',
|
|
Data => {
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# get old data to be given to post event handler
|
|
my $ChangeData = $Self->ChangeGet(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# update CAB
|
|
if ( exists $Param{CABAgents} || exists $Param{CABCustomers} ) {
|
|
return if !$Self->ChangeCABUpdate(%Param);
|
|
}
|
|
|
|
# set the change dynamic fields
|
|
KEY:
|
|
for my $Key ( sort keys %Param ) {
|
|
|
|
next KEY if $Key !~ m{ \A DynamicField_(.*) \z }xms;
|
|
|
|
# save the real name of the dynamic field (without prefix)
|
|
my $DynamicFieldName = $1;
|
|
|
|
# get the dynamic field config
|
|
my $DynamicFieldConfig = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldGet(
|
|
Name => $DynamicFieldName,
|
|
);
|
|
|
|
# write value to dynamic field
|
|
my $Success = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->ValueSet(
|
|
DynamicFieldConfig => $DynamicFieldConfig,
|
|
ObjectID => $Param{ChangeID},
|
|
Value => $Param{$Key},
|
|
UserID => $Param{UserID},
|
|
);
|
|
}
|
|
|
|
# map update attributes to column names
|
|
my %Attribute = (
|
|
ChangeTitle => 'title',
|
|
Description => 'description',
|
|
Justification => 'justification',
|
|
ChangeStateID => 'change_state_id',
|
|
ChangeManagerID => 'change_manager_id',
|
|
ChangeBuilderID => 'change_builder_id',
|
|
CategoryID => 'category_id',
|
|
ImpactID => 'impact_id',
|
|
PriorityID => 'priority_id',
|
|
RequestedTime => 'requested_time',
|
|
DescriptionPlain => 'description_plain',
|
|
JustificationPlain => 'justification_plain',
|
|
);
|
|
|
|
# build SQL to update change
|
|
my $SQL = 'UPDATE change_item SET ';
|
|
my @Bind;
|
|
|
|
ATTRIBUTE:
|
|
for my $Attribute ( sort keys %Attribute ) {
|
|
|
|
# preserve the old value, when the column isn't in function parameters
|
|
next ATTRIBUTE if !exists $Param{$Attribute};
|
|
|
|
# param checking has already been done, so this is safe
|
|
$SQL .= "$Attribute{$Attribute} = ?, ";
|
|
push @Bind, \$Param{$Attribute};
|
|
}
|
|
|
|
$SQL .= 'change_time = current_timestamp, change_by = ? ';
|
|
push @Bind, \$Param{UserID};
|
|
$SQL .= 'WHERE id = ?';
|
|
push @Bind, \$Param{ChangeID};
|
|
|
|
# update change
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => $SQL,
|
|
Bind => \@Bind,
|
|
);
|
|
|
|
# delete cache
|
|
for my $Key (
|
|
'ChangeGet::ID::' . $Param{ChangeID},
|
|
'ChangeList',
|
|
'ChangeLookup::ChangeID::' . $Param{ChangeID},
|
|
'ChangeLookup::ChangeNumber::' . $ChangeData->{ChangeNumber},
|
|
)
|
|
{
|
|
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
|
Type => $Self->{CacheType},
|
|
Key => $Key,
|
|
);
|
|
}
|
|
|
|
# trigger ChangeUpdatePost-Event
|
|
$Self->EventHandler(
|
|
Event => 'ChangeUpdatePost',
|
|
Data => {
|
|
OldChangeData => $ChangeData,
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 ChangeGet()
|
|
|
|
Return a change as a hash reference.
|
|
When the C<workorder> does not exist, a false value is returned.
|
|
The optional option C<LogNo> turns off logging when the change does not exist.
|
|
|
|
my $Change = $ChangeObject->ChangeGet(
|
|
ChangeID => 123,
|
|
UserID => 1,
|
|
LogNo => 1, # optional, turns off logging when the change does not exist
|
|
);
|
|
|
|
The returned hash reference contains the following elements:
|
|
|
|
$Change{ChangeID}
|
|
$Change{ChangeNumber}
|
|
$Change{ChangeStateID}
|
|
$Change{ChangeState} # fetched from the general catalog
|
|
$Change{ChangeStateSignal} # fetched from SysConfig
|
|
$Change{ChangeTitle}
|
|
$Change{Description}
|
|
$Change{DescriptionPlain}
|
|
$Change{Justification}
|
|
$Change{JustificationPlain}
|
|
$Change{ChangeManagerID}
|
|
$Change{ChangeBuilderID}
|
|
$Change{CategoryID}
|
|
$Change{Category}
|
|
$Change{ImpactID}
|
|
$Change{Impact}
|
|
$Change{PriorityID}
|
|
$Change{Priority}
|
|
$Change{WorkOrderIDs} # array reference with WorkOrderIDs, sorted by WorkOrderNumber
|
|
$Change{WorkOrderCount} # number of workorders
|
|
$Change{CABAgents} # array reference with CAB Agent UserIDs
|
|
$Change{CABCustomers} # array reference with CAB CustomerUserIDs
|
|
$Change{PlannedStartTime} # determined from the workorders
|
|
$Change{PlannedEndTime} # determined from the workorders
|
|
$Change{ActualStartTime} # determined from the workorders
|
|
$Change{ActualEndTime} # determined from the workorders
|
|
$Change{PlannedEffort} # determined from the workorders
|
|
$Change{AccountedTime} # determined from the workorders
|
|
$Change{RequestedTime}
|
|
$Change{DynamicField_X}
|
|
$Change{DynamicField_Y}
|
|
$Change{CreateTime}
|
|
$Change{CreateBy}
|
|
$Change{ChangeTime}
|
|
$Change{ChangeBy}
|
|
|
|
=cut
|
|
|
|
sub ChangeGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Attribute (qw(ChangeID UserID)) {
|
|
if ( !$Param{$Attribute} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Attribute!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check cache
|
|
my $CacheKey = 'ChangeGet::ID::' . $Param{ChangeID};
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
|
|
my %ChangeData;
|
|
|
|
if ($Cache) {
|
|
%ChangeData = %{$Cache};
|
|
}
|
|
else {
|
|
|
|
# get data from database
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT id, change_number, title, '
|
|
. 'description, description_plain, '
|
|
. 'justification, justification_plain, '
|
|
. 'change_state_id, change_manager_id, change_builder_id, '
|
|
. 'category_id, impact_id, priority_id, '
|
|
. 'create_time, create_by, change_time, change_by, '
|
|
. 'requested_time '
|
|
. 'FROM change_item '
|
|
. 'WHERE id = ? ',
|
|
Bind => [ \$Param{ChangeID} ],
|
|
Limit => 1,
|
|
);
|
|
|
|
# fetch the result
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
$ChangeData{ChangeID} = $Row[0];
|
|
$ChangeData{ChangeNumber} = $Row[1];
|
|
$ChangeData{ChangeTitle} = defined( $Row[2] ) ? $Row[2] : '';
|
|
$ChangeData{Description} = defined( $Row[3] ) ? $Row[3] : '';
|
|
$ChangeData{DescriptionPlain} = defined( $Row[4] ) ? $Row[4] : '';
|
|
$ChangeData{Justification} = defined( $Row[5] ) ? $Row[5] : '';
|
|
$ChangeData{JustificationPlain} = defined( $Row[6] ) ? $Row[6] : '';
|
|
$ChangeData{ChangeStateID} = $Row[7];
|
|
$ChangeData{ChangeManagerID} = $Row[8];
|
|
$ChangeData{ChangeBuilderID} = $Row[9];
|
|
$ChangeData{CategoryID} = $Row[10];
|
|
$ChangeData{ImpactID} = $Row[11];
|
|
$ChangeData{PriorityID} = $Row[12];
|
|
$ChangeData{CreateTime} = $Row[13];
|
|
$ChangeData{CreateBy} = $Row[14];
|
|
$ChangeData{ChangeTime} = $Row[15];
|
|
$ChangeData{ChangeBy} = $Row[16];
|
|
$ChangeData{RequestedTime} = $Row[17];
|
|
}
|
|
|
|
# check error
|
|
if ( !%ChangeData ) {
|
|
if ( !$Param{LogNo} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Change with ID $Param{ChangeID} does not exist.",
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
# cleanup time stamps (some databases are using e. g. 2008-02-25 22:03:00.000000)
|
|
TIMEFIELD:
|
|
for my $Timefield ( 'CreateTime', 'ChangeTime', 'RequestedTime' ) {
|
|
next TIMEFIELD if !$ChangeData{$Timefield};
|
|
$ChangeData{$Timefield}
|
|
=~ s{ \A ( \d\d\d\d - \d\d - \d\d \s \d\d:\d\d:\d\d ) \. .+? \z }{$1}xms;
|
|
}
|
|
|
|
# get all dynamic fields for the object type ITSMChange
|
|
my $DynamicFieldList = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
|
|
ObjectType => 'ITSMChange',
|
|
);
|
|
|
|
DYNAMICFIELD:
|
|
for my $DynamicFieldConfig ( @{$DynamicFieldList} ) {
|
|
|
|
# validate each dynamic field
|
|
next DYNAMICFIELD if !$DynamicFieldConfig;
|
|
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
|
|
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
|
|
next DYNAMICFIELD if !IsHashRefWithData( $DynamicFieldConfig->{Config} );
|
|
|
|
# get the current value for each dynamic field
|
|
my $Value = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->ValueGet(
|
|
DynamicFieldConfig => $DynamicFieldConfig,
|
|
ObjectID => $Param{ChangeID},
|
|
);
|
|
|
|
# set the dynamic field name and value into the change data hash
|
|
$ChangeData{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $Value // '';
|
|
}
|
|
|
|
# set cache (change data exists at this point, it was checked before)
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => \%ChangeData,
|
|
TTL => $Self->{CacheTTL},
|
|
);
|
|
}
|
|
|
|
# set name of change state
|
|
if ( $ChangeData{ChangeStateID} ) {
|
|
$ChangeData{ChangeState} = $Self->ChangeStateLookup(
|
|
ChangeStateID => $ChangeData{ChangeStateID},
|
|
);
|
|
}
|
|
|
|
# set names for CIP
|
|
for my $Type (qw(Category Impact Priority)) {
|
|
if ( $ChangeData{"${Type}ID"} ) {
|
|
$ChangeData{$Type} = $Self->ChangeCIPLookup(
|
|
ID => $ChangeData{"${Type}ID"},
|
|
Type => $Type,
|
|
);
|
|
}
|
|
}
|
|
|
|
# set the change state signal
|
|
if ( $ChangeData{ChangeState} ) {
|
|
|
|
# get all change state signals
|
|
my $StateSignal = $Kernel::OM->Get('Kernel::Config')->Get('ITSMChange::State::Signal');
|
|
|
|
$ChangeData{ChangeStateSignal} = $StateSignal->{ $ChangeData{ChangeState} };
|
|
}
|
|
|
|
# get CAB data
|
|
my $CAB = $Self->ChangeCABGet(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
) || {};
|
|
|
|
# add result to change data
|
|
%ChangeData = ( %ChangeData, %{$CAB} );
|
|
|
|
# get all workorder ids for this change
|
|
my $WorkOrderIDsRef = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder')->WorkOrderList(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# add result to change data
|
|
$ChangeData{WorkOrderIDs} = $WorkOrderIDsRef || [];
|
|
$ChangeData{WorkOrderCount} = scalar @{ $ChangeData{WorkOrderIDs} };
|
|
|
|
# get planned effort and accounted time for the change
|
|
my $ChangeEfforts = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder')->WorkOrderChangeEffortsGet(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# merge effort hash with change hash
|
|
if (
|
|
$ChangeEfforts
|
|
&& ref $ChangeEfforts eq 'HASH'
|
|
&& %{$ChangeEfforts}
|
|
)
|
|
{
|
|
%ChangeData = ( %ChangeData, %{$ChangeEfforts} );
|
|
}
|
|
|
|
# get timestamps for the change
|
|
my $ChangeTime = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder')->WorkOrderChangeTimeGet(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# merge time hash with change hash
|
|
if (
|
|
$ChangeTime
|
|
&& ref $ChangeTime eq 'HASH'
|
|
&& %{$ChangeTime}
|
|
)
|
|
{
|
|
%ChangeData = ( %ChangeData, %{$ChangeTime} );
|
|
}
|
|
|
|
return \%ChangeData;
|
|
}
|
|
|
|
=head2 ChangeCABUpdate()
|
|
|
|
Add or update the CAB of a change.
|
|
One or both of CABAgents and CABCustomers must be passed.
|
|
Passing a reference to an empty array deletes the part of the CAB (CABAgents or CABCustomers)
|
|
When agents or customers are passed multiple times, they will be inserted only once.
|
|
|
|
my $Success = $ChangeObject->ChangeCABUpdate(
|
|
ChangeID => 123,
|
|
CABAgents => [ 1, 2, 4 ], # UserIDs (optional)
|
|
CABCustomers => [ 'tt', 'mm' ], # CustomerUserIDs (optional)
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeCABUpdate {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Attribute (qw(ChangeID UserID)) {
|
|
if ( !$Param{$Attribute} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Attribute!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# either CABAgents of CABCustomers or both must be passed
|
|
if ( !$Param{CABAgents} && !$Param{CABCustomers} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need parameter CABAgents or CABCustomers!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# CABAgents and CABCustomers must be array references
|
|
for my $Attribute (qw(CABAgents CABCustomers)) {
|
|
if ( $Param{$Attribute} && ref $Param{$Attribute} ne 'ARRAY' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The parameter $Attribute is not an array reference!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check if CABAgents and CABCustomers exist in the agents and customer databases
|
|
return if !$Self->_CheckChangeParams(%Param);
|
|
|
|
# trigger ChangeCABUpdatePre-Event
|
|
$Self->EventHandler(
|
|
Event => 'ChangeCABUpdatePre',
|
|
Data => {
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# get old CAB data to be given to post event handler
|
|
my $ChangeCABData = $Self->ChangeCABGet(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# enter the CAB Agents
|
|
if ( $Param{CABAgents} ) {
|
|
|
|
# remove all current users from cab table
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'DELETE FROM change_cab '
|
|
. 'WHERE change_id = ? '
|
|
. 'AND user_id IS NOT NULL',
|
|
Bind => [ \$Param{ChangeID} ],
|
|
);
|
|
|
|
# filter out unique users
|
|
my %UniqueUsers = map { $_ => 1 } @{ $Param{CABAgents} };
|
|
|
|
# add user to cab table
|
|
for my $UserID ( sort keys %UniqueUsers ) {
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'INSERT INTO change_cab ( change_id, user_id ) VALUES ( ?, ? )',
|
|
Bind => [ \$Param{ChangeID}, \$UserID ],
|
|
);
|
|
}
|
|
}
|
|
|
|
# enter the CAB Customers
|
|
if ( $Param{CABCustomers} ) {
|
|
|
|
# remove all current customer users from cab table
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'DELETE FROM change_cab '
|
|
. 'WHERE change_id = ? '
|
|
. 'AND customer_user_id IS NOT NULL',
|
|
Bind => [ \$Param{ChangeID} ],
|
|
);
|
|
|
|
# filter out unique customer users
|
|
my %UniqueCustomerUsers = map { $_ => 1 } @{ $Param{CABCustomers} };
|
|
|
|
# add user to cab table
|
|
for my $CustomerUserID ( sort keys %UniqueCustomerUsers ) {
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'INSERT INTO change_cab ( change_id, customer_user_id ) VALUES ( ?, ? )',
|
|
Bind => [ \$Param{ChangeID}, \$CustomerUserID ],
|
|
);
|
|
}
|
|
}
|
|
|
|
# delete cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
|
Type => $Self->{CacheType},
|
|
Key => 'ChangeCABGet::ID::' . $Param{ChangeID},
|
|
);
|
|
|
|
# trigger ChangeCABUpdatePost-Event
|
|
$Self->EventHandler(
|
|
Event => 'ChangeCABUpdatePost',
|
|
Data => {
|
|
OldChangeCABData => $ChangeCABData,
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 ChangeCABGet()
|
|
|
|
Return the CAB of a change as a hashref, where the values are arrayrefs.
|
|
The returned array references are sorted.
|
|
|
|
my $ChangeCAB = $ChangeObject->ChangeCABGet(
|
|
ChangeID => 123,
|
|
UserID => 1,
|
|
);
|
|
|
|
Returns:
|
|
|
|
$ChangeCAB = {
|
|
CABAgents => [ 1, 2, 4 ],
|
|
CABCustomers => [ 'aa', 'bb' ],
|
|
}
|
|
|
|
=cut
|
|
|
|
sub ChangeCABGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Argument (qw(ChangeID UserID)) {
|
|
if ( !$Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Argument!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# cab data
|
|
my %CAB = (
|
|
CABAgents => [],
|
|
CABCustomers => [],
|
|
);
|
|
|
|
# check cache
|
|
my $CacheKey = 'ChangeCABGet::ID::' . $Param{ChangeID};
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
|
|
if ($Cache) {
|
|
|
|
# get data from cache
|
|
%CAB = %{$Cache};
|
|
}
|
|
|
|
else {
|
|
|
|
# get data
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT id, change_id, user_id, customer_user_id '
|
|
. 'FROM change_cab WHERE change_id = ?',
|
|
Bind => [ \$Param{ChangeID} ],
|
|
);
|
|
|
|
my $ErrorCABID;
|
|
|
|
ROW:
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
my $CABID = $Row[0];
|
|
my $ChangeID = $Row[1];
|
|
my $UserID = $Row[2];
|
|
my $CustomerUserID = $Row[3];
|
|
|
|
# error check if both columns are filled
|
|
if ( $UserID && $CustomerUserID ) {
|
|
$ErrorCABID = $CABID;
|
|
next ROW;
|
|
}
|
|
|
|
# add data to CAB
|
|
if ($UserID) {
|
|
push @{ $CAB{CABAgents} }, $UserID;
|
|
}
|
|
elsif ($CustomerUserID) {
|
|
push @{ $CAB{CABCustomers} }, $CustomerUserID;
|
|
}
|
|
}
|
|
|
|
# error check if both columns are filled
|
|
if ($ErrorCABID) {
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message =>
|
|
"CAB table entry with ID $ErrorCABID contains UserID and CustomerUserID! "
|
|
. 'Only one at a time is allowed!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# sort the results
|
|
@{ $CAB{CABAgents} } = sort @{ $CAB{CABAgents} };
|
|
@{ $CAB{CABCustomers} } = sort @{ $CAB{CABCustomers} };
|
|
|
|
# set cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => \%CAB,
|
|
TTL => $Self->{CacheTTL},
|
|
);
|
|
}
|
|
|
|
return \%CAB;
|
|
}
|
|
|
|
=head2 ChangeCABDelete()
|
|
|
|
Delete the CAB of a change.
|
|
|
|
my $Success = $ChangeObject->ChangeCABDelete(
|
|
ChangeID => 123,
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeCABDelete {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Attribute (qw(ChangeID UserID)) {
|
|
if ( !$Param{$Attribute} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Attribute!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# trigger ChangeCABDeletePre-Event
|
|
$Self->EventHandler(
|
|
Event => 'ChangeCABDeletePre',
|
|
Data => {
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# get old CAB data to be given to post event handler
|
|
my $ChangeCABData = $Self->ChangeCABGet(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# delete CAB
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'DELETE FROM change_cab WHERE change_id = ?',
|
|
Bind => [ \$Param{ChangeID} ],
|
|
);
|
|
|
|
# delete cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
|
Type => $Self->{CacheType},
|
|
Key => 'ChangeCABGet::ID::' . $Param{ChangeID},
|
|
);
|
|
|
|
# trigger ChangeCABDeletePost-Event
|
|
$Self->EventHandler(
|
|
Event => 'ChangeCABDeletePost',
|
|
Data => {
|
|
OldChangeCABData => $ChangeCABData,
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 ChangeLookup()
|
|
|
|
Return the change id when the change number is passed.
|
|
Return the change number when the change id is passed.
|
|
When no change id or change number is found, then the undefined value is returned.
|
|
|
|
my $ChangeID = $ChangeObject->ChangeLookup(
|
|
ChangeNumber => '2009091742000465',
|
|
);
|
|
|
|
or
|
|
|
|
my $ChangeNumber = $ChangeObject->ChangeLookup(
|
|
ChangeID => 42,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeLookup {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# the change id or the change number must be passed
|
|
if ( !$Param{ChangeID} && !$Param{ChangeNumber} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need the ChangeID or the ChangeNumber!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# only one of change id and change number can be passed
|
|
if ( $Param{ChangeID} && $Param{ChangeNumber} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need either the ChangeID or the ChangeNumber, not both!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get change id
|
|
if ( $Param{ChangeNumber} ) {
|
|
|
|
my $ChangeID;
|
|
|
|
# check cache
|
|
my $CacheKey = 'ChangeLookup::ChangeNumber::' . $Param{ChangeNumber};
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
|
|
if ($Cache) {
|
|
|
|
# get data from cache
|
|
$ChangeID = $Cache;
|
|
}
|
|
|
|
else {
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT id FROM change_item WHERE change_number = ?',
|
|
Bind => [ \$Param{ChangeNumber} ],
|
|
Limit => 1,
|
|
);
|
|
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
$ChangeID = $Row[0];
|
|
}
|
|
|
|
# set cache only if change id exists
|
|
if ($ChangeID) {
|
|
|
|
# set cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => $ChangeID,
|
|
TTL => $Self->{CacheTTL},
|
|
);
|
|
}
|
|
}
|
|
|
|
return $ChangeID;
|
|
}
|
|
|
|
# get change number
|
|
elsif ( $Param{ChangeID} ) {
|
|
|
|
my $ChangeNumber;
|
|
|
|
# check cache
|
|
my $CacheKey = 'ChangeLookup::ChangeID::' . $Param{ChangeID};
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
|
|
if ($Cache) {
|
|
|
|
# get data from cache
|
|
$ChangeNumber = $Cache;
|
|
}
|
|
|
|
else {
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT change_number FROM change_item WHERE id = ?',
|
|
Bind => [ \$Param{ChangeID} ],
|
|
Limit => 1,
|
|
);
|
|
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
$ChangeNumber = $Row[0];
|
|
}
|
|
|
|
# set cache only if change number exists
|
|
if ($ChangeNumber) {
|
|
|
|
# set cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => $ChangeNumber,
|
|
TTL => $Self->{CacheTTL},
|
|
);
|
|
}
|
|
}
|
|
|
|
return $ChangeNumber;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
=head2 ChangeList()
|
|
|
|
Return a change id list of all changes as an array reference.
|
|
|
|
my $ChangeIDsRef = $ChangeObject->ChangeList(
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeList {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need UserID!',
|
|
);
|
|
return;
|
|
}
|
|
my @ChangeIDs;
|
|
|
|
# check cache
|
|
my $CacheKey = 'ChangeList';
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
|
|
if ($Cache) {
|
|
|
|
# get change ids from cache
|
|
@ChangeIDs = @{$Cache};
|
|
}
|
|
|
|
else {
|
|
|
|
# get change ids
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT id FROM change_item',
|
|
);
|
|
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
push @ChangeIDs, $Row[0];
|
|
}
|
|
|
|
# set cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => \@ChangeIDs,
|
|
TTL => $Self->{CacheTTL},
|
|
);
|
|
}
|
|
|
|
return \@ChangeIDs;
|
|
}
|
|
|
|
=head2 ChangeSearch()
|
|
|
|
Returns either a list, as an arrayref, or a count of found change ids.
|
|
The count of results is returned when the parameter C<Result = 'COUNT'> is passed.
|
|
|
|
The search criteria are logically AND connected.
|
|
When a list is passed as C<criterium>, the individual members are OR connected.
|
|
When an undef or a reference to an empty array is passed, then the search C<criterium>
|
|
is ignored.
|
|
|
|
my $ChangeIDsRef = $ChangeObject->ChangeSearch(
|
|
ChangeNumber => '2009100112345778', # (optional)
|
|
|
|
ChangeTitle => 'Replacement of slow mail server', # (optional)
|
|
Description => 'New mail server is faster', # (optional)
|
|
Justification => 'Old mail server too slow', # (optional)
|
|
|
|
# array parameters are used with logical OR operator
|
|
ChangeStateIDs => [ 11, 12, 13 ], # (optional)
|
|
ChangeStates => [ 'requested', 'failed' ], # (optional)
|
|
ChangeManagerIDs => [ 1, 2, 3 ], # (optional)
|
|
ChangeBuilderIDs => [ 5, 7, 4 ], # (optional)
|
|
CreateBy => [ 5, 2, 3 ], # (optional)
|
|
ChangeBy => [ 3, 2, 1 ], # (optional)
|
|
WorkOrderAgentIDs => [ 6, 2 ], # (optional)
|
|
CABAgents => [ 9, 13 ], # (optional)
|
|
CABCustomers => [ 'tt', 'xx' ], # (optional)
|
|
Categories => [ '1 very low', '2 low' ], # (optional)
|
|
CategoryIDs => [ 135, 173 ], # (optional)
|
|
Impacts => [ '1 very low', '2 low' ], # (optional)
|
|
ImpactIDs => [ 136, 174 ], # (optional)
|
|
Priorities => [ '1 very low', '2 low' ], # (optional)
|
|
PriorityIDs => [ 137, 175 ], # (optional)
|
|
|
|
# DynamicFields (both change and C<workorder> dynamic fields are possible)
|
|
# At least one operator must be specified. Operators will be connected with AND,
|
|
# values in an operator with OR.
|
|
# You can also pass more than one argument to an operator: ['value1', 'value2']
|
|
DynamicField_FieldNameX => {
|
|
Equals => 123,
|
|
Like => 'value*', # "equals" operator with wildcard support
|
|
GreaterThan => '2001-01-01 01:01:01',
|
|
GreaterThanEquals => '2001-01-01 01:01:01',
|
|
SmallerThan => '2002-02-02 02:02:02',
|
|
SmallerThanEquals => '2002-02-02 02:02:02',
|
|
}
|
|
|
|
# search in text fields of C<workorder> object
|
|
WorkOrderTitle => 'Boot Mailserver', # (optional)
|
|
WorkOrderInstruction => 'Press the button.', # (optional)
|
|
WorkOrderReport => 'Mailserver has booted.', # (optional)
|
|
|
|
# search in C<workorder> (array params)
|
|
WorkOrderStates => [ 'accepted', 'ready' ], # (optional)
|
|
WorkOrderStateIDs => [ 1, 2, 3 ], # (optional)
|
|
WorkOrderTypes => [ 'workorder', 'backout', 'approval' ], # (optional)
|
|
WorkOrderTypeIDs => [ 5, 6, 7 ], # (optional)
|
|
|
|
# changes with planned start time after ...
|
|
PlannedStartTimeNewerDate => '2006-01-09 00:00:01', # (optional)
|
|
# changes with planned start time before then ....
|
|
PlannedStartTimeOlderDate => '2006-01-19 23:59:59', # (optional)
|
|
|
|
# changes with planned end time after ...
|
|
PlannedEndTimeNewerDate => '2006-01-09 00:00:01', # (optional)
|
|
# changes with planned end time before then ....
|
|
PlannedEndTimeOlderDate => '2006-01-19 23:59:59', # (optional)
|
|
|
|
# changes with actual start time after ...
|
|
ActualStartTimeNewerDate => '2006-01-09 00:00:01', # (optional)
|
|
# changes with actual start time before then ....
|
|
ActualStartTimeOlderDate => '2006-01-19 23:59:59', # (optional)
|
|
|
|
# changes with actual end time after ...
|
|
ActualEndTimeNewerDate => '2006-01-09 00:00:01', # (optional)
|
|
# changes with actual end time before then ....
|
|
ActualEndTimeOlderDate => '2006-01-19 23:59:59', # (optional)
|
|
|
|
# changes with created time after ...
|
|
CreateTimeNewerDate => '2006-01-09 00:00:01', # (optional)
|
|
# changes with created time before then ....
|
|
CreateTimeOlderDate => '2006-01-19 23:59:59', # (optional)
|
|
|
|
# changes with changed time after ...
|
|
ChangeTimeNewerDate => '2006-01-09 00:00:01', # (optional)
|
|
# changes with changed time before then ....
|
|
ChangeTimeOlderDate => '2006-01-19 23:59:59', # (optional)
|
|
|
|
# changes with requested time after ...
|
|
RequestedTimeNewerDate => '2006-01-09 00:00:01', # (optional)
|
|
# changes with requested time before then ....
|
|
RequestedTimeOlderDate => '2006-01-19 23:59:59', # (optional)
|
|
|
|
OrderBy => [ 'ChangeID', 'ChangeManagerID' ], # (optional)
|
|
# ignored when the result type is 'COUNT'
|
|
# default: [ 'ChangeID' ]
|
|
# (ChangeID, ChangeNumber, ChangeTitle, ChangeStateID,
|
|
# ChangeManagerID, ChangeBuilderID,
|
|
# CategoryID, ImpactID, PriorityID
|
|
# PlannedStartTime, PlannedEndTime,
|
|
# ActualStartTime, ActualEndTime, RequestedTime,
|
|
# 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)
|
|
# ignored when the result type is 'COUNT'
|
|
# default: [ 'Down' ]
|
|
# (Down | Up)
|
|
|
|
UsingWildcards => 1, # (optional)
|
|
# (0 | 1) default 1
|
|
|
|
Result => 'ARRAY' || 'COUNT', # (optional)
|
|
# default: ARRAY, returns an array of change ids
|
|
# COUNT returns a scalar with the number of found changes
|
|
|
|
Limit => 100, # (optional)
|
|
# ignored when the result type is 'COUNT'
|
|
|
|
MirrorDB => 1, # (optional)
|
|
# (0 | 1) default 0
|
|
# if set to 1 and ITSMChange::ChangeSearch::MirrorDB
|
|
# is activated and a mirror db is configured in
|
|
# Core::MirrorDB::DSN the change search will then use
|
|
# the mirror db
|
|
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeSearch {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need UserID!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# verify that all passed array parameters contain an arrayref
|
|
ARGUMENT:
|
|
for my $Argument (
|
|
qw(
|
|
OrderBy
|
|
OrderByDirection
|
|
ChangeStateIDs
|
|
ChangeStates
|
|
ChangeManagerIDs
|
|
ChangeBuilderIDs
|
|
CABAgents
|
|
CABCustomers
|
|
WorkOrderAgentIDs
|
|
WorkOrderStates
|
|
WorkOrderStateIDs
|
|
WorkOrderTypes
|
|
WorkOrderTypeIDs
|
|
CreateBy
|
|
ChangeBy
|
|
Categories
|
|
CategoryIDs
|
|
Impacts
|
|
ImpactIDs
|
|
Priorities
|
|
PriorityIDs
|
|
)
|
|
)
|
|
{
|
|
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 a local database object
|
|
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
|
|
|
# get config object
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# if we need to do a change search on an external mirror database
|
|
if (
|
|
$Param{MirrorDB}
|
|
&& $ConfigObject->Get('ITSMChange::ChangeSearch::MirrorDB')
|
|
&& $ConfigObject->Get('Core::MirrorDB::DSN')
|
|
&& $ConfigObject->Get('Core::MirrorDB::User')
|
|
&& $ConfigObject->Get('Core::MirrorDB::Password')
|
|
)
|
|
{
|
|
|
|
# create an extra database object for the mirror db
|
|
my $ExtraDatabaseObject = Kernel::System::DB->new(
|
|
DatabaseDSN => $ConfigObject->Get('Core::MirrorDB::DSN'),
|
|
DatabaseUser => $ConfigObject->Get('Core::MirrorDB::User'),
|
|
DatabasePw => $ConfigObject->Get('Core::MirrorDB::Password'),
|
|
);
|
|
|
|
# check error
|
|
if ( !$ExtraDatabaseObject ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Could not create database object for MirrorDB!',
|
|
);
|
|
return;
|
|
}
|
|
$DBObject = $ExtraDatabaseObject;
|
|
}
|
|
|
|
# define order table
|
|
my %OrderByTable = (
|
|
ChangeID => 'c.id',
|
|
ChangeNumber => 'c.change_number',
|
|
ChangeTitle => 'c.title',
|
|
ChangeStateID => 'c.change_state_id',
|
|
ChangeManagerID => 'c.change_manager_id',
|
|
ChangeBuilderID => 'c.change_builder_id',
|
|
CategoryID => 'c.category_id',
|
|
ImpactID => 'c.impact_id',
|
|
PriorityID => 'c.priority_id',
|
|
CreateTime => 'c.create_time',
|
|
CreateBy => 'c.create_by',
|
|
ChangeTime => 'c.change_time',
|
|
ChangeBy => 'c.change_by',
|
|
RequestedTime => 'c.requested_time',
|
|
PlannedStartTime => 'MIN(wo1.planned_start_time)',
|
|
PlannedEndTime => 'MAX(wo1.planned_end_time)',
|
|
ActualStartTime => 'MIN(wo1.actual_start_time)',
|
|
ActualEndTime => 'MAX(wo1.actual_end_time)',
|
|
);
|
|
|
|
# check if OrderBy contains only unique valid values
|
|
my %OrderBySeen;
|
|
for my $OrderBy ( @{ $Param{OrderBy} } ) {
|
|
|
|
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;
|
|
}
|
|
|
|
# set the default behaviour for the return type
|
|
my $Result = $Param{Result} || 'ARRAY';
|
|
|
|
# check whether all of the given ChangeStateIDs are valid
|
|
return if !$Self->_CheckChangeStateIDs( ChangeStateIDs => $Param{ChangeStateIDs} );
|
|
|
|
# check whether all of the given CategoryIDs, ImpactIDs and PriorityIDs are valid
|
|
for my $Type (qw(Category Impact Priority)) {
|
|
return if !$Self->_CheckChangeCIPIDs(
|
|
IDs => $Param{"${Type}IDs"},
|
|
Type => $Type,
|
|
);
|
|
}
|
|
|
|
# look up and thus check the States
|
|
for my $ChangeState ( @{ $Param{ChangeStates} } ) {
|
|
|
|
# look up the ID for the name
|
|
my $ChangeStateID = $Self->ChangeStateLookup(
|
|
ChangeState => $ChangeState,
|
|
);
|
|
|
|
# check whether the ID was found, whether the name exists
|
|
if ( !$ChangeStateID ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The change state $ChangeState is not known!",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
push @{ $Param{ChangeStateIDs} }, $ChangeStateID;
|
|
}
|
|
|
|
# look up and thus check the CIPs
|
|
my %CIPSingular2Plural = (
|
|
Category => 'Categories',
|
|
Impact => 'Impacts',
|
|
Priority => 'Priorities',
|
|
);
|
|
|
|
for my $CIPSingular ( sort keys %CIPSingular2Plural ) {
|
|
for my $CIP ( @{ $Param{ $CIPSingular2Plural{$CIPSingular} } } ) {
|
|
|
|
# look up the ID for the name
|
|
my $CIPID = $Self->ChangeCIPLookup(
|
|
CIP => $CIP,
|
|
Type => $CIPSingular,
|
|
);
|
|
|
|
# check whether the ID was found, whether the name exists
|
|
if ( !$CIPID ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The $CIPSingular $CIP is not known!",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
push @{ $Param{"${CIPSingular}IDs"} }, $CIPID;
|
|
}
|
|
}
|
|
|
|
# check workorder states - if given
|
|
return
|
|
if !$Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder')->WorkOrderStateIDsCheck(
|
|
WorkOrderStateIDs => $Param{WorkOrderStateIDs},
|
|
);
|
|
|
|
# look up and thus check the workorder states
|
|
for my $WorkOrderState ( @{ $Param{WorkOrderStates} } ) {
|
|
|
|
# look up the ID for the name
|
|
my $WorkOrderStateID = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder')->WorkOrderStateLookup(
|
|
WorkOrderState => $WorkOrderState,
|
|
);
|
|
|
|
# check whether the ID was found, whether the name exists
|
|
if ( !$WorkOrderStateID ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The workorder state $WorkOrderState is not known!",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
push @{ $Param{WorkOrderStateIDs} }, $WorkOrderStateID;
|
|
}
|
|
|
|
# look up and thus check the workorder types
|
|
for my $WorkOrderType ( @{ $Param{WorkOrderTypes} } ) {
|
|
|
|
# look up the ID for the name
|
|
my $WorkOrderTypeID = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder')->WorkOrderTypeLookup(
|
|
WorkOrderType => $WorkOrderType,
|
|
);
|
|
|
|
# check whether the ID was found, whether the name exists
|
|
if ( !$WorkOrderTypeID ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The workorder type $WorkOrderType is not known!",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
push @{ $Param{WorkOrderTypeIDs} }, $WorkOrderTypeID;
|
|
}
|
|
|
|
my @SQLWhere; # assemble the conditions used in the WHERE clause
|
|
my @SQLHaving; # assemble the conditions used in the HAVING clause
|
|
my @InnerJoinTables; # keep track of the tables that need to be inner joined
|
|
my @OuterJoinTables; # keep track of the tables that need to be outer joined
|
|
|
|
# check all configured change dynamic fields, build lookup hash by name
|
|
my %ChangeDynamicFieldName2Config;
|
|
my $ChangeDynamicFields = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
|
|
ObjectType => 'ITSMChange',
|
|
);
|
|
for my $DynamicField ( @{$ChangeDynamicFields} ) {
|
|
$ChangeDynamicFieldName2Config{ $DynamicField->{Name} } = $DynamicField;
|
|
}
|
|
|
|
# check all configured workorder dynamic fields, build lookup hash by name
|
|
my %WorkOrderDynamicFieldName2Config;
|
|
my $WorkOrderDynamicFields = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
|
|
ObjectType => 'ITSMWorkOrder',
|
|
);
|
|
for my $DynamicField ( @{$WorkOrderDynamicFields} ) {
|
|
$WorkOrderDynamicFieldName2Config{ $DynamicField->{Name} } = $DynamicField;
|
|
}
|
|
|
|
# add string params to the WHERE clause
|
|
my %StringParams = (
|
|
|
|
# strings in change_item
|
|
ChangeNumber => 'c.change_number',
|
|
ChangeTitle => 'c.title',
|
|
Description => 'c.description_plain',
|
|
Justification => 'c.justification_plain',
|
|
|
|
# strings in change_workorder
|
|
WorkOrderTitle => 'wo2.title',
|
|
WorkOrderInstruction => 'wo2.instruction_plain',
|
|
WorkOrderReport => 'wo2.report_plain',
|
|
);
|
|
|
|
# add string params to sql-where-array
|
|
STRINGPARAM:
|
|
for my $StringParam ( sort keys %StringParams ) {
|
|
|
|
# check string params for useful values, the string '0' is allowed
|
|
next STRINGPARAM if !exists $Param{$StringParam};
|
|
next STRINGPARAM if !defined $Param{$StringParam};
|
|
next STRINGPARAM if $Param{$StringParam} eq '';
|
|
|
|
# quote
|
|
$Param{$StringParam} = $DBObject->Quote( $Param{$StringParam} );
|
|
|
|
# check if a CLOB field is used in oracle
|
|
# Fix/Workaround for ORA-00932: inconsistent datatypes: expected - got CLOB
|
|
my $ForceLikeSearchForSpecialFields;
|
|
if (
|
|
$Self->{DBType} eq 'oracle'
|
|
&& ( $StringParam eq 'Description' || $StringParam eq 'Justification' )
|
|
)
|
|
{
|
|
$ForceLikeSearchForSpecialFields = 1;
|
|
}
|
|
|
|
# wildcards are used (or LIKE search is forced for some special fields on oracle)
|
|
if ( $Param{UsingWildcards} || $ForceLikeSearchForSpecialFields ) {
|
|
|
|
# get like escape string needed for some databases (e.g. oracle)
|
|
my $LikeEscapeString = $DBObject->GetDatabaseFunction('LikeEscapeString');
|
|
|
|
# Quote
|
|
$Param{$StringParam} = $DBObject->Quote( $Param{$StringParam}, 'Like' );
|
|
|
|
# replace * with %
|
|
$Param{$StringParam} =~ s{ \*+ }{%}xmsg;
|
|
|
|
# do not use string params which contain only %
|
|
next STRINGPARAM if $Param{$StringParam} =~ m{ \A %* \z }xms;
|
|
|
|
push @SQLWhere,
|
|
"LOWER($StringParams{$StringParam}) LIKE LOWER('$Param{$StringParam}') $LikeEscapeString";
|
|
}
|
|
|
|
# no wildcards are used
|
|
else {
|
|
push @SQLWhere,
|
|
"LOWER($StringParams{$StringParam}) = LOWER('$Param{$StringParam}')";
|
|
}
|
|
|
|
if ( $StringParams{$StringParam} =~ m{ wo2 }xms ) {
|
|
|
|
# the change_workorder table needs to be joined, when it occurs in the WHERE clause
|
|
push @InnerJoinTables, 'wo2';
|
|
}
|
|
}
|
|
|
|
# build sql for dynamic fields
|
|
my $SQLDynamicFieldInnerJoins = ''; # join-statements
|
|
my $SQLDynamicFieldWhere = ''; # where-clause
|
|
my $DynamicFieldJoinCounter = 1;
|
|
|
|
DYNAMICFIELD:
|
|
for my $DynamicField ( @{$ChangeDynamicFields}, @{$WorkOrderDynamicFields} ) {
|
|
|
|
my $SearchParam = $Param{ "DynamicField_" . $DynamicField->{Name} };
|
|
|
|
next DYNAMICFIELD if ( !$SearchParam );
|
|
next DYNAMICFIELD if ( ref $SearchParam ne 'HASH' );
|
|
|
|
my $NeedJoin;
|
|
|
|
for my $Operator ( sort keys %{$SearchParam} ) {
|
|
|
|
my @SearchParams = ( ref $SearchParam->{$Operator} eq 'ARRAY' )
|
|
? @{ $SearchParam->{$Operator} }
|
|
: ( $SearchParam->{$Operator} );
|
|
|
|
my $SQLDynamicFieldWhereSub = '';
|
|
if ($SQLDynamicFieldWhere) {
|
|
$SQLDynamicFieldWhereSub = ' AND (';
|
|
}
|
|
else {
|
|
$SQLDynamicFieldWhereSub = ' (';
|
|
}
|
|
|
|
my $Counter = 0;
|
|
TEXT:
|
|
for my $Text (@SearchParams) {
|
|
next TEXT if ( !defined $Text || $Text eq '' );
|
|
|
|
$Text =~ s/\*/%/gi;
|
|
|
|
# check search attribute, we do not need to search for *
|
|
next TEXT if $Text =~ /^\%{1,3}$/;
|
|
|
|
# validate data type
|
|
my $ValidateSuccess = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->ValueValidate(
|
|
DynamicFieldConfig => $DynamicField,
|
|
Value => $Text,
|
|
UserID => $Param{UserID} || 1,
|
|
);
|
|
if ( !$ValidateSuccess ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message =>
|
|
"Search not executed due to invalid value '"
|
|
. $Text
|
|
. "' on field '"
|
|
. $DynamicField->{Name}
|
|
. "'!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
if ($Counter) {
|
|
$SQLDynamicFieldWhereSub .= ' OR ';
|
|
}
|
|
$SQLDynamicFieldWhereSub
|
|
.= $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->SearchSQLGet(
|
|
DynamicFieldConfig => $DynamicField,
|
|
TableAlias => "dfv$DynamicFieldJoinCounter",
|
|
Operator => $Operator,
|
|
SearchTerm => $Text,
|
|
);
|
|
|
|
$Counter++;
|
|
}
|
|
$SQLDynamicFieldWhereSub .= ') ';
|
|
if ($Counter) {
|
|
$SQLDynamicFieldWhere .= $SQLDynamicFieldWhereSub;
|
|
$NeedJoin = 1;
|
|
}
|
|
}
|
|
|
|
if ($NeedJoin) {
|
|
|
|
if ( $DynamicField->{ObjectType} eq 'ITSMChange' ) {
|
|
|
|
# join the table for this dynamic field
|
|
$SQLDynamicFieldInnerJoins
|
|
.= "INNER JOIN dynamic_field_value dfv$DynamicFieldJoinCounter
|
|
ON (c.id = dfv$DynamicFieldJoinCounter.object_id
|
|
AND dfv$DynamicFieldJoinCounter.field_id = " .
|
|
$DBObject->Quote( $DynamicField->{ID}, 'Integer' ) . ") ";
|
|
}
|
|
|
|
elsif ( $DynamicField->{ObjectType} eq 'ITSMWorkOrder' ) {
|
|
|
|
# always join the workorder table for inner joins with the change table
|
|
# it does not matter if already contained in @InnerJoinTables
|
|
# as double entries are filtered out later
|
|
push @InnerJoinTables, 'wo2';
|
|
|
|
$SQLDynamicFieldInnerJoins
|
|
.= "INNER JOIN dynamic_field_value dfv$DynamicFieldJoinCounter
|
|
ON (wo2.id = dfv$DynamicFieldJoinCounter.object_id
|
|
AND dfv$DynamicFieldJoinCounter.field_id = " .
|
|
$DBObject->Quote( $DynamicField->{ID}, 'Integer' ) . ") ";
|
|
}
|
|
|
|
$DynamicFieldJoinCounter++;
|
|
}
|
|
}
|
|
|
|
# set array params
|
|
my %ArrayParams = (
|
|
ChangeStateIDs => 'c.change_state_id',
|
|
ChangeManagerIDs => 'c.change_manager_id',
|
|
ChangeBuilderIDs => 'c.change_builder_id',
|
|
CategoryIDs => 'c.category_id',
|
|
ImpactIDs => 'c.impact_id',
|
|
PriorityIDs => 'c.priority_id',
|
|
CreateBy => 'c.create_by',
|
|
ChangeBy => 'c.change_by',
|
|
);
|
|
|
|
# add array params to sql-where-array
|
|
ARRAYPARAM:
|
|
for my $ArrayParam ( sort keys %ArrayParams ) {
|
|
|
|
# ignore empty lists
|
|
next ARRAYPARAM if !@{ $Param{$ArrayParam} };
|
|
|
|
# quote
|
|
for my $OneParam ( @{ $Param{$ArrayParam} } ) {
|
|
$OneParam = $DBObject->Quote( $OneParam, 'Integer' );
|
|
}
|
|
|
|
# create string
|
|
my $InString = join ', ', @{ $Param{$ArrayParam} };
|
|
|
|
push @SQLWhere, "$ArrayParams{$ArrayParam} IN ($InString)";
|
|
}
|
|
|
|
# set time params
|
|
my %TimeParams = (
|
|
|
|
# times in change_item
|
|
CreateTimeNewerDate => 'c.create_time >=',
|
|
CreateTimeOlderDate => 'c.create_time <=',
|
|
ChangeTimeNewerDate => 'c.change_time >=',
|
|
ChangeTimeOlderDate => 'c.change_time <=',
|
|
RequestedTimeNewerDate => 'c.requested_time >=',
|
|
RequestedTimeOlderDate => 'c.requested_time <=',
|
|
|
|
# times in change_workorder
|
|
PlannedStartTimeNewerDate => 'min(wo1.planned_start_time) >=',
|
|
PlannedStartTimeOlderDate => 'min(wo1.planned_start_time) <=',
|
|
PlannedEndTimeNewerDate => 'max(wo1.planned_end_time) >=',
|
|
PlannedEndTimeOlderDate => 'max(wo1.planned_end_time) <=',
|
|
ActualStartTimeNewerDate => 'min(wo1.actual_start_time) >=',
|
|
ActualStartTimeOlderDate => 'min(wo1.actual_start_time) <=',
|
|
ActualEndTimeNewerDate => 'max(wo1.actual_end_time) >=',
|
|
ActualEndTimeOlderDate => 'max(wo1.actual_end_time) <=',
|
|
);
|
|
|
|
# check and add time params to WHERE or HAVING clause
|
|
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 => "The parameter $TimeParam has an invalid date format!",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
$Param{$TimeParam} = $DBObject->Quote( $Param{$TimeParam} );
|
|
|
|
if ( $TimeParams{$TimeParam} =~ m{ wo1 }xms ) {
|
|
|
|
# the change_workorder table needs to be joined, when it occurs in the HAVING clause
|
|
push @SQLHaving, "$TimeParams{$TimeParam} '$Param{$TimeParam}'";
|
|
push @OuterJoinTables, 'wo1';
|
|
}
|
|
else {
|
|
|
|
# the time attributes of change_item show up in the WHERE clause
|
|
push @SQLWhere, "$TimeParams{$TimeParam} '$Param{$TimeParam}'";
|
|
}
|
|
}
|
|
|
|
# conditions for CAB searches
|
|
my %CABParams = (
|
|
CABAgents => 'cab1.user_id',
|
|
CABCustomers => 'cab2.customer_user_id',
|
|
);
|
|
|
|
# add cab params to sql-where-array
|
|
CABPARAM:
|
|
for my $CABParam ( sort keys %CABParams ) {
|
|
next CABPARAM if !@{ $Param{$CABParam} };
|
|
|
|
# quote
|
|
for my $OneParam ( @{ $Param{$CABParam} } ) {
|
|
$OneParam = $DBObject->Quote($OneParam);
|
|
}
|
|
|
|
if ( $CABParam eq 'CABAgents' ) {
|
|
|
|
# CABAgent is a integer, so no quotes are needed
|
|
my $InString = join ', ', @{ $Param{$CABParam} };
|
|
push @SQLWhere, "$CABParams{$CABParam} IN ($InString)";
|
|
push @InnerJoinTables, 'cab1';
|
|
}
|
|
elsif ( $CABParam eq 'CABCustomers' ) {
|
|
|
|
# CABCustomer is a string, so the single quotes are needed
|
|
my $InString = join ', ', map {"'$_'"} @{ $Param{$CABParam} };
|
|
push @SQLWhere, "$CABParams{$CABParam} IN ($InString)";
|
|
push @InnerJoinTables, 'cab2';
|
|
}
|
|
}
|
|
|
|
# workorder array params
|
|
my %WorkOrderArrayParams = (
|
|
WorkOrderAgentIDs => 'workorder_agent_id',
|
|
WorkOrderStateIDs => 'workorder_state_id',
|
|
WorkOrderTypeIDs => 'workorder_type_id',
|
|
);
|
|
|
|
# add workorder params to sql-where-array
|
|
WORKORDERPARAM:
|
|
for my $WorkOrderParam ( sort keys %WorkOrderArrayParams ) {
|
|
|
|
next WORKORDERPARAM if !@{ $Param{$WorkOrderParam} };
|
|
|
|
# quote as integer
|
|
for my $OneParam ( @{ $Param{$WorkOrderParam} } ) {
|
|
$OneParam = $DBObject->Quote( $OneParam, 'Integer' );
|
|
}
|
|
|
|
# create string
|
|
my $InString = join ', ', @{ $Param{$WorkOrderParam} };
|
|
my $ColumnName = $WorkOrderArrayParams{$WorkOrderParam};
|
|
|
|
push @SQLWhere, "wo2.$ColumnName IN ( $InString )";
|
|
push @InnerJoinTables, 'wo2';
|
|
}
|
|
|
|
# define which parameter require a join with workorder table
|
|
my %TableRequiresJoin = (
|
|
PlannedStartTime => 1,
|
|
PlannedEndTime => 1,
|
|
ActualStartTime => 1,
|
|
ActualEndTime => 1,
|
|
);
|
|
|
|
# delete the OrderBy parameter when the result type is 'COUNT'
|
|
if ( $Result eq 'COUNT' ) {
|
|
$Param{OrderBy} = [];
|
|
}
|
|
|
|
# assemble the ORDER BY clause
|
|
my @SQLOrderBy;
|
|
my @SQLAliases; # order by aliases, be on the save side with MySQL
|
|
my $Count = 0;
|
|
for my $OrderBy ( @{ $Param{OrderBy} } ) {
|
|
|
|
# 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
|
|
if ( $OrderByTable{$OrderBy} =~ m{ wo1 }xms ) {
|
|
push @SQLAliases, "$OrderByTable{$OrderBy} as alias_$OrderBy";
|
|
push @SQLOrderBy, "alias_$OrderBy $Direction";
|
|
}
|
|
else {
|
|
push @SQLOrderBy, "$OrderByTable{$OrderBy} $Direction";
|
|
}
|
|
|
|
# for some order fields, we need to make sure, that the wo1 table is joined
|
|
if ( $TableRequiresJoin{$OrderBy} ) {
|
|
push @OuterJoinTables, 'wo1';
|
|
}
|
|
}
|
|
continue {
|
|
$Count++;
|
|
}
|
|
|
|
# if there is a possibility that the ordering is not determined
|
|
# we add an descending ordering by id
|
|
if ( !grep { $_ eq 'ChangeID' } ( @{ $Param{OrderBy} } ) ) {
|
|
push @SQLOrderBy, "$OrderByTable{ChangeID} DESC";
|
|
}
|
|
|
|
# assemble the SQL query
|
|
my $SQL = 'SELECT ' . join( ', ', ( 'c.id', @SQLAliases ) ) . ' FROM change_item c ';
|
|
|
|
# modify SQL when the result type is 'COUNT', and when there are no joins
|
|
if ( $Result eq 'COUNT' && !@InnerJoinTables && !@OuterJoinTables ) {
|
|
$SQL = 'SELECT COUNT(c.id) FROM change_item c ';
|
|
@SQLOrderBy = ();
|
|
}
|
|
|
|
# add the joins
|
|
my %LongTableName = (
|
|
wo1 => 'change_workorder',
|
|
wo2 => 'change_workorder',
|
|
cab1 => 'change_cab',
|
|
cab2 => 'change_cab',
|
|
);
|
|
my %TableSeen;
|
|
|
|
INNER_JOIN_TABLE:
|
|
for my $Table (@InnerJoinTables) {
|
|
|
|
# do not join a table twice
|
|
next INNER_JOIN_TABLE if $TableSeen{$Table};
|
|
|
|
$TableSeen{$Table} = 1;
|
|
|
|
if ( !$LongTableName{$Table} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Encountered invalid inner join table '$Table'!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
$SQL .= "INNER JOIN $LongTableName{$Table} $Table ON $Table.change_id = c.id ";
|
|
}
|
|
|
|
# add the dynamic field inner join statements
|
|
$SQL .= $SQLDynamicFieldInnerJoins;
|
|
|
|
OUTER_JOIN_TABLE:
|
|
for my $Table (@OuterJoinTables) {
|
|
|
|
# do not join a table twice, when a table has been inner joined, no outer join is necessary
|
|
next OUTER_JOIN_TABLE if $TableSeen{$Table};
|
|
|
|
# remember that this table is joined already
|
|
$TableSeen{$Table} = 1;
|
|
|
|
# check error
|
|
if ( !$LongTableName{$Table} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Encountered invalid outer join table '$Table'!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
$SQL .= "LEFT OUTER JOIN $LongTableName{$Table} $Table ON $Table.change_id = c.id ";
|
|
}
|
|
|
|
# add the WHERE clause
|
|
if (@SQLWhere) {
|
|
$SQL .= 'WHERE ';
|
|
$SQL .= join ' AND ', map {"( $_ )"} @SQLWhere;
|
|
$SQL .= ' ';
|
|
if ($SQLDynamicFieldWhere) {
|
|
$SQL .= ' AND ' . $SQLDynamicFieldWhere;
|
|
}
|
|
}
|
|
else {
|
|
if ($SQLDynamicFieldWhere) {
|
|
$SQL .= ' WHERE ' . $SQLDynamicFieldWhere;
|
|
}
|
|
}
|
|
|
|
# we need to group whenever there is a join
|
|
if (
|
|
scalar @InnerJoinTables
|
|
|| $SQLDynamicFieldWhere
|
|
|| scalar @OuterJoinTables
|
|
)
|
|
{
|
|
$SQL .= 'GROUP BY c.id ';
|
|
|
|
# add the orderby columns also to the group by clause, as this is correct SQL
|
|
# and some DBs like PostgreSQL are more strict than others
|
|
# this is the bugfix for bug# 5825 http://bugs.otrs.org/show_bug.cgi?id=5825
|
|
if (@SQLOrderBy) {
|
|
|
|
ORDERBY:
|
|
for my $OrderBy (@SQLOrderBy) {
|
|
|
|
# get the column from a string that looks like: c.change_number ASC
|
|
if ( $OrderBy =~ m{ \A (\S+) }xms ) {
|
|
|
|
# get the column part of the string
|
|
my $Column = $1;
|
|
|
|
# do not include the c.id column again, as this is already done before
|
|
next ORDERBY if $Column eq 'c.id';
|
|
|
|
# do not include aliases of aggregate functions (min/max)
|
|
next ORDERBY if $Column =~ m{ \A alias_ }xms;
|
|
|
|
# add the column to the group by clause
|
|
$SQL .= ", $Column ";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# add the HAVING clause
|
|
if (@SQLHaving) {
|
|
$SQL .= 'HAVING ';
|
|
$SQL .= join ' AND ', map {"( $_ )"} @SQLHaving;
|
|
$SQL .= ' ';
|
|
}
|
|
|
|
# add the ORDER BY clause
|
|
if (@SQLOrderBy) {
|
|
$SQL .= 'ORDER BY ';
|
|
$SQL .= join ', ', @SQLOrderBy;
|
|
$SQL .= ' ';
|
|
}
|
|
|
|
# ignore the parameter 'Limit' when result type is 'COUNT'
|
|
if ( $Result eq 'COUNT' ) {
|
|
delete $Param{Limit};
|
|
}
|
|
|
|
# ask database
|
|
return if !$DBObject->Prepare(
|
|
SQL => $SQL,
|
|
Limit => $Param{Limit},
|
|
);
|
|
|
|
# fetch the result
|
|
my @IDs;
|
|
while ( my @Row = $DBObject->FetchrowArray() ) {
|
|
push @IDs, $Row[0];
|
|
}
|
|
|
|
if (
|
|
$Result eq 'COUNT'
|
|
&& !@InnerJoinTables
|
|
&& !$SQLDynamicFieldWhere
|
|
&& !@OuterJoinTables
|
|
)
|
|
{
|
|
|
|
# return the COUNT(c.id) attribute
|
|
return $IDs[0] || 0;
|
|
}
|
|
elsif ( $Result eq 'COUNT' ) {
|
|
|
|
# return the count as the number of IDs
|
|
return scalar @IDs;
|
|
}
|
|
else {
|
|
return \@IDs;
|
|
}
|
|
}
|
|
|
|
=head2 ChangeDelete()
|
|
|
|
Delete a change.
|
|
|
|
This function first removes all links and attachments to the given change.
|
|
Then it gets a list of all C<workorders> of the change and
|
|
calls C<WorkorderDelete()> for each C<workorder>, which will delete
|
|
all links and all attachments to the C<workorders>.
|
|
Then it deletes the CAB.
|
|
After that the change is removed.
|
|
The history of this change will be deleted during the handling of the
|
|
triggered ChangeDeletePost-event.
|
|
|
|
my $Success = $ChangeObject->ChangeDelete(
|
|
ChangeID => 123,
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeDelete {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Attribute (qw(ChangeID UserID)) {
|
|
if ( !$Param{$Attribute} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Attribute!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# trigger ChangeDeletePre-Event
|
|
$Self->EventHandler(
|
|
Event => 'ChangeDeletePre',
|
|
Data => {
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# the change does not exist, when it can't be looked up
|
|
return if !$Self->ChangeLookup(
|
|
ChangeID => $Param{ChangeID},
|
|
);
|
|
|
|
# delete all links to this change
|
|
return if !$Kernel::OM->Get('Kernel::System::LinkObject')->LinkDeleteAll(
|
|
Object => 'ITSMChange',
|
|
Key => $Param{ChangeID},
|
|
UserID => 1,
|
|
);
|
|
|
|
# get the list of attachments and delete them
|
|
my @Attachments = $Self->ChangeAttachmentList(
|
|
ChangeID => $Param{ChangeID},
|
|
);
|
|
for my $Filename (@Attachments) {
|
|
return if !$Self->ChangeAttachmentDelete(
|
|
ChangeID => $Param{ChangeID},
|
|
Filename => $Filename,
|
|
UserID => $Param{UserID},
|
|
);
|
|
}
|
|
|
|
# get change data to get the workorder ids
|
|
my $ChangeData = $Self->ChangeGet(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# check if change contains workorders
|
|
if (
|
|
$ChangeData
|
|
&& ref $ChangeData eq 'HASH'
|
|
&& $ChangeData->{WorkOrderIDs}
|
|
&& ref $ChangeData->{WorkOrderIDs} eq 'ARRAY'
|
|
)
|
|
{
|
|
|
|
# delete the workorders
|
|
for my $WorkOrderID ( @{ $ChangeData->{WorkOrderIDs} } ) {
|
|
|
|
my $DeleteSuccess = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder')->WorkOrderDelete(
|
|
WorkOrderID => $WorkOrderID,
|
|
NoNumberCalc => 1,
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
return if !$DeleteSuccess;
|
|
}
|
|
}
|
|
|
|
# delete the CAB
|
|
return if !$Self->ChangeCABDelete(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# get all dynamic fields for the object type ITSMChange
|
|
my $DynamicFieldList = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
|
|
ObjectType => 'ITSMChange',
|
|
Valid => 0,
|
|
);
|
|
|
|
# delete dynamicfield values for this change
|
|
DYNAMICFIELD:
|
|
for my $DynamicFieldConfig ( @{$DynamicFieldList} ) {
|
|
|
|
next DYNAMICFIELD if !$DynamicFieldConfig;
|
|
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
|
|
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
|
|
next DYNAMICFIELD if !IsHashRefWithData( $DynamicFieldConfig->{Config} );
|
|
|
|
$Kernel::OM->Get('Kernel::System::DynamicField::Backend')->ValueDelete(
|
|
DynamicFieldConfig => $DynamicFieldConfig,
|
|
ObjectID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
}
|
|
|
|
# delete cache
|
|
for my $Key (
|
|
'ChangeGet::ID::' . $Param{ChangeID},
|
|
'ChangeList',
|
|
'ChangeLookup::ChangeID::' . $Param{ChangeID},
|
|
'ChangeLookup::ChangeNumber::' . $ChangeData->{ChangeNumber},
|
|
)
|
|
{
|
|
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
|
Type => $Self->{CacheType},
|
|
Key => $Key,
|
|
);
|
|
}
|
|
|
|
# trigger ChangeDeletePost-Event
|
|
# this must be done before deleting the change from the database,
|
|
# because of a foreign key constraint in the change_history table
|
|
$Self->EventHandler(
|
|
Event => 'ChangeDeletePost',
|
|
Data => {
|
|
OldChangeData => $ChangeData,
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# delete the change
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'DELETE FROM change_item WHERE id = ?',
|
|
Bind => [ \$Param{ChangeID} ],
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 ChangeStateLookup()
|
|
|
|
This method does a lookup for a change state. If a change state id is given,
|
|
it returns the name of the change state. If a change state name is given,
|
|
the appropriate id is returned.
|
|
|
|
my $ChangeState = $ChangeObject->ChangeStateLookup(
|
|
ChangeStateID => 1234,
|
|
);
|
|
|
|
my $ChangeStateID = $ChangeObject->ChangeStateLookup(
|
|
ChangeState => 'accepted',
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeStateLookup {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# either ChangeStateID or State must be passed
|
|
if ( !$Param{ChangeStateID} && !$Param{ChangeState} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need ChangeStateID or ChangeState!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
if ( $Param{ChangeStateID} && $Param{ChangeState} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need ChangeStateID OR ChangeState - not both!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get the change states from the general catalog
|
|
my $StateList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
|
|
Class => 'ITSM::ChangeManagement::Change::State',
|
|
);
|
|
|
|
# convert state list into a lookup hash
|
|
my %StateID2Name;
|
|
if ( $StateList && ref $StateList eq 'HASH' && %{$StateList} ) {
|
|
%StateID2Name = %{$StateList};
|
|
}
|
|
|
|
# check the state hash
|
|
if ( !%StateID2Name ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Could not retrieve change states from the general catalog.',
|
|
);
|
|
return;
|
|
}
|
|
if ( $Param{ChangeStateID} ) {
|
|
return $StateID2Name{ $Param{ChangeStateID} };
|
|
}
|
|
else {
|
|
|
|
# reverse key - value pairs to have the name as keys
|
|
my %StateName2ID = reverse %StateID2Name;
|
|
|
|
return $StateName2ID{ $Param{ChangeState} };
|
|
}
|
|
}
|
|
|
|
=head2 ChangePossibleStatesGet()
|
|
|
|
This method returns a list of possible change states.
|
|
If ChangeID is omitted, the complete list of change states is returned.
|
|
If ChangeID is given, the list of possible change states for this
|
|
change is returned.
|
|
|
|
my $ChangeStateList = $ChangeObject->ChangePossibleStatesGet(
|
|
ChangeID => 123, # (optional)
|
|
UserID => 1,
|
|
);
|
|
|
|
The return value is a reference to an array of hashrefs. The element 'Key' is then
|
|
the ChangeStateID and the element 'Value' is the name of the state. The array elements
|
|
are sorted by state id.
|
|
|
|
my $ChangeStateList = [
|
|
{
|
|
Key => 156,
|
|
Value => 'approved',
|
|
},
|
|
{
|
|
Key => 157,
|
|
Value => 'in progress',
|
|
},
|
|
];
|
|
|
|
=cut
|
|
|
|
sub ChangePossibleStatesGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need UserID!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get change state list
|
|
my $StateList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
|
|
Class => 'ITSM::ChangeManagement::Change::State',
|
|
) || {};
|
|
|
|
# to store an array of hash refs
|
|
my @ArrayHashRef;
|
|
|
|
# if ChangeID is given, only use possible next states as defined in state machine
|
|
if ( $Param{ChangeID} ) {
|
|
|
|
# get change data
|
|
my $Change = $Self->ChangeGet(
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# check for state lock
|
|
my $StateLock;
|
|
$StateLock = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMCondition')->ConditionMatchStateLock(
|
|
ObjectName => 'ITSMChange',
|
|
Selector => $Param{ChangeID},
|
|
StateID => $Change->{ChangeStateID},
|
|
UserID => $Param{UserID},
|
|
);
|
|
|
|
# set as default state current change state
|
|
my @NextStateIDs = ( $Change->{ChangeStateID} );
|
|
|
|
# check if reachable change end states should be allowed for locked change states
|
|
my $ChangeEndStatesAllowed = $Kernel::OM->Get('Kernel::Config')->Get('ITSMChange::StateLock::AllowEndStates');
|
|
|
|
if ($ChangeEndStatesAllowed) {
|
|
|
|
# set as default state current state and all possible end states
|
|
my $EndStateIDsRef
|
|
= $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMStateMachine')->StateTransitionGetEndStates(
|
|
StateID => $Change->{ChangeStateID},
|
|
Class => 'ITSM::ChangeManagement::Change::State',
|
|
) || [];
|
|
@NextStateIDs = sort ( @{$EndStateIDsRef}, $Change->{ChangeStateID} );
|
|
}
|
|
|
|
# get possible next states if no state lock
|
|
if ( !$StateLock ) {
|
|
|
|
# get the possible next state ids
|
|
my $NextStateIDsRef = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMStateMachine')->StateTransitionGet(
|
|
StateID => $Change->{ChangeStateID},
|
|
Class => 'ITSM::ChangeManagement::Change::State',
|
|
) || [];
|
|
|
|
# add current change state id to list
|
|
@NextStateIDs = sort ( @{$NextStateIDsRef}, $Change->{ChangeStateID} );
|
|
}
|
|
|
|
# assemble the array of hash refs with only possible next states
|
|
STATEID:
|
|
for my $StateID (@NextStateIDs) {
|
|
|
|
next STATEID if !$StateID;
|
|
|
|
push @ArrayHashRef, {
|
|
Key => $StateID,
|
|
Value => $StateList->{$StateID},
|
|
};
|
|
}
|
|
|
|
return \@ArrayHashRef;
|
|
}
|
|
|
|
# assemble the array of hash refs with all next states
|
|
for my $StateID ( sort keys %{$StateList} ) {
|
|
push @ArrayHashRef, {
|
|
Key => $StateID,
|
|
Value => $StateList->{$StateID},
|
|
};
|
|
}
|
|
|
|
return \@ArrayHashRef;
|
|
}
|
|
|
|
=head2 ChangePossibleCIPGet()
|
|
|
|
This method returns a list of possible categories, impacts or priorities.
|
|
|
|
my $CIPList = $ChangeObject->ChangePossibleCIPGet(
|
|
Type => 'Category', # Category|Impact|Priority
|
|
UserID => 1,
|
|
);
|
|
|
|
The return value is a reference to an array of hashrefs. The Element 'Key' is then
|
|
the ID and the element 'Value' is the name of the category, impact or priority.
|
|
The array elements are sorted by id in ascending order.
|
|
|
|
my $CIPList = [
|
|
{
|
|
Key => 156,
|
|
Value => '1 very low',
|
|
},
|
|
{
|
|
Key => 157,
|
|
Value => '2 low',
|
|
},
|
|
];
|
|
|
|
=cut
|
|
|
|
sub ChangePossibleCIPGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need UserID!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check Type param for valid values
|
|
if (
|
|
!$Param{Type}
|
|
|| ( $Param{Type} ne 'Category' && $Param{Type} ne 'Impact' && $Param{Type} ne 'Priority' )
|
|
)
|
|
{
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'The param Type must be either "Category" or "Impact" or "Priority"!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get item list for the requested type
|
|
my $CIPList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
|
|
Class => 'ITSM::ChangeManagement::' . $Param{Type},
|
|
) || {};
|
|
|
|
# assemble an array of hash refs
|
|
my @ArrayHashRef;
|
|
for my $ID ( sort { $CIPList->{$a} cmp $CIPList->{$b} } keys %{$CIPList} ) {
|
|
push @ArrayHashRef, {
|
|
Key => $ID,
|
|
Value => $CIPList->{$ID},
|
|
};
|
|
}
|
|
|
|
return \@ArrayHashRef;
|
|
}
|
|
|
|
=head2 ChangeCIPLookup()
|
|
|
|
This method does a lookup for a change category, impact or priority.
|
|
If a change C<CIP-ID> is given, it returns the name of the C<CIP>.
|
|
If a change C<CIP> name is given, the appropriate ID is returned.
|
|
|
|
my $Name = $ChangeObject->ChangeCIPLookup(
|
|
ID => 1234,
|
|
Type => 'Priority',
|
|
);
|
|
|
|
my $ID = $ChangeObject->ChangeCIPLookup(
|
|
CIP => '1 very low',
|
|
Type => 'Category',
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeCIPLookup {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# either ID or CIP must be passed
|
|
if ( !$Param{ID} && !$Param{CIP} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need ID or CIP!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check that not both ID and CIP are given
|
|
if ( $Param{ID} && $Param{CIP} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need either ID OR CIP - not both!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check Type param for valid values
|
|
if (
|
|
!$Param{Type}
|
|
|| ( $Param{Type} ne 'Category' && $Param{Type} ne 'Impact' && $Param{Type} ne 'Priority' )
|
|
)
|
|
{
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'The param Type must be either "Category" or "Impact" or "Priority"!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get change CIP from general catalog
|
|
# mapping of the id to the name
|
|
my %ChangeCIP = %{
|
|
$Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
|
|
Class => 'ITSM::ChangeManagement::' . $Param{Type},
|
|
) || {}
|
|
};
|
|
|
|
if ( $Param{ID} ) {
|
|
return $ChangeCIP{ $Param{ID} };
|
|
}
|
|
else {
|
|
|
|
# reverse key - value pairs to have the name as keys
|
|
my %ReversedChangeCIP = reverse %ChangeCIP;
|
|
|
|
return $ReversedChangeCIP{ $Param{CIP} };
|
|
}
|
|
}
|
|
|
|
=head2 Permission()
|
|
|
|
Returns whether the agent C<UserID> has permissions of the type C<Type>
|
|
on the change C<ChangeID>. The parameters are passed on to
|
|
the permission modules that were registered in the permission registry.
|
|
The standard permission registry is B<ITSMChange::Permission>, but
|
|
that can be overridden with the parameter C<PermissionRegistry>.
|
|
|
|
The optional option C<LogNo> turns off logging when access was denied.
|
|
This is useful when the method is used for checking whether a link or an action should be shown.
|
|
|
|
my $Access = $ChangeObject->Permission(
|
|
UserID => 123,
|
|
Type => 'ro', # 'ro' and 'rw' are supported
|
|
Action => 'AgentITSMChangeEdit', # optional
|
|
ChangeID => 3333, # optional, do not pass for 'ChangeAdd'
|
|
PermissionRegistry => 'ITSMChange::Permission',
|
|
# optional with default 'ITSMChange::Permission'
|
|
LogNo => 1, # optional, turns off logging when access is denied
|
|
);
|
|
|
|
=cut
|
|
|
|
sub Permission {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Argument (qw(Type UserID)) {
|
|
if ( !$Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Argument!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# There are valid cases when no ChangeID is passed.
|
|
# E.g. for ChangeAdd() or ChangeSearch().
|
|
$Param{ChangeID} ||= '';
|
|
|
|
# the place where the permission modules are registerd can be overridden by a parameter
|
|
my $Registry = $Param{PermissionRegistry} || 'ITSMChange::Permission';
|
|
|
|
# run the relevant permission modules
|
|
if ( ref $Kernel::OM->Get('Kernel::Config')->Get($Registry) eq 'HASH' ) {
|
|
|
|
my %Modules = %{ $Kernel::OM->Get('Kernel::Config')->Get($Registry) };
|
|
|
|
MODULE:
|
|
for my $Module ( sort keys %Modules ) {
|
|
|
|
# log try of load module
|
|
if ( $Self->{Debug} > 1 ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'debug',
|
|
Message => "Try to load module: $Modules{$Module}->{Module}!",
|
|
);
|
|
}
|
|
|
|
# load module
|
|
next MODULE
|
|
if !$Kernel::OM->Get('Kernel::System::Main')->Require( $Modules{$Module}->{Module} );
|
|
|
|
# create object
|
|
my $ModuleObject = $Modules{$Module}->{Module}->new();
|
|
|
|
# ask for the opinion of the Permission module
|
|
my $Access = $ModuleObject->Run(%Param);
|
|
|
|
# Grant overall permission,
|
|
# when the module granted a sufficient permission.
|
|
if ( $Access && $Modules{$Module}->{Granted} ) {
|
|
if ( $Self->{Debug} > 0 ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'debug',
|
|
Message => "Granted '$Param{Type}' access for "
|
|
. "UserID: $Param{UserID} on "
|
|
. "ChangeID '$Param{ChangeID}' "
|
|
. "through $Modules{$Module}->{Module} (no more checks)!",
|
|
);
|
|
}
|
|
|
|
# grant permission
|
|
return 1;
|
|
}
|
|
|
|
# Deny overall permission,
|
|
# when the module denied a required permission.
|
|
if ( !$Access && $Modules{$Module}->{Required} ) {
|
|
if ( !$Param{LogNo} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'notice',
|
|
Message => "Denied '$Param{Type}' access for "
|
|
. "UserID: $Param{UserID} on "
|
|
. "ChangeID '$Param{ChangeID}' "
|
|
. "because $Modules{$Module}->{Module} is required!",
|
|
);
|
|
}
|
|
|
|
# deny permission
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Deny access when neither a 'Granted'-Check nor a 'Required'-Check has reached a conclusion.
|
|
if ( !$Param{LogNo} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'notice',
|
|
Message => "Permission denied (UserID: $Param{UserID} '$Param{Type}' "
|
|
. "on ChangeID: $Param{ChangeID})!",
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
=head2 ChangeAttachmentAdd()
|
|
|
|
Add an attachment to the given change.
|
|
|
|
my $Success = $ChangeObject->ChangeAttachmentAdd(
|
|
ChangeID => 123, # the ChangeID becomes part of the file path
|
|
Filename => 'filename',
|
|
Content => 'content',
|
|
ContentType => 'text/plain',
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeAttachmentAdd {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Needed (qw(ChangeID 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 => "Change/$Param{ChangeID}/$Param{Filename}",
|
|
Mode => 'binary',
|
|
Content => \$Param{Content},
|
|
Preferences => {
|
|
ContentID => $Param{ContentID},
|
|
ContentType => $Param{ContentType},
|
|
ChangeID => $Param{ChangeID},
|
|
UserID => $Param{UserID},
|
|
},
|
|
);
|
|
|
|
# check for error
|
|
if ($Success) {
|
|
|
|
# trigger AttachmentAdd-Event
|
|
$Self->EventHandler(
|
|
Event => 'ChangeAttachmentAddPost',
|
|
Data => {
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Cannot add attachment for change $Param{ChangeID}",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 ChangeAttachmentDelete()
|
|
|
|
Delete the given file from the virtual filesystem.
|
|
|
|
my $Success = $ChangeObject->ChangeAttachmentDelete(
|
|
ChangeID => 123, # used in event handling, e.g. for logging the history
|
|
Filename => 'Projectplan.pdf', # identifies the attachment (together with the ChangeID)
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeAttachmentDelete {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Needed (qw(ChangeID Filename UserID)) {
|
|
if ( !$Param{$Needed} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
# add prefix
|
|
my $Filename = 'Change/' . $Param{ChangeID} . '/' . $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 => 'ChangeAttachmentDeletePost',
|
|
Data => {
|
|
%Param,
|
|
},
|
|
UserID => $Param{UserID},
|
|
);
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Cannot delete attachment $Filename!",
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
return $Success;
|
|
}
|
|
|
|
=head2 ChangeAttachmentGet()
|
|
|
|
This method returns information about one specific attachment.
|
|
|
|
my $Attachment = $ChangeObject->ChangeAttachmentGet(
|
|
ChangeID => 4,
|
|
Filename => 'test.txt',
|
|
);
|
|
|
|
returns
|
|
|
|
{
|
|
Preferences => {
|
|
AllPreferences => 'test',
|
|
},
|
|
Filename => 'test.txt',
|
|
Content => 'hallo',
|
|
ContentType => 'text/plain',
|
|
Filesize => '123 KBytes',
|
|
Type => 'attachment',
|
|
}
|
|
|
|
=cut
|
|
|
|
sub ChangeAttachmentGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Argument (qw(ChangeID Filename)) {
|
|
if ( !$Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Argument!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# add prefix
|
|
my $Filename = 'Change/' . $Param{ChangeID} . '/' . $Param{Filename};
|
|
|
|
# find all attachments of this change
|
|
my @Attachments = $Kernel::OM->Get('Kernel::System::VirtualFS')->Find(
|
|
Filename => $Filename,
|
|
Preferences => {
|
|
ChangeID => $Param{ChangeID},
|
|
},
|
|
);
|
|
|
|
# return error if file does not exist
|
|
if ( !@Attachments ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Message => "No such attachment ($Filename)! May be an attack!!!",
|
|
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 ChangeAttachmentList()
|
|
|
|
Returns an array with all attachments of the given change.
|
|
|
|
my @Attachments = $ChangeObject->ChangeAttachmentList(
|
|
ChangeID => 123,
|
|
);
|
|
|
|
returns
|
|
|
|
@Attachments = (
|
|
'filename.txt',
|
|
'other_file.pdf',
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeAttachmentList {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{ChangeID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need ChangeID!',
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
# find all attachments of this change
|
|
my @Attachments = $Kernel::OM->Get('Kernel::System::VirtualFS')->Find(
|
|
Preferences => {
|
|
ChangeID => $Param{ChangeID},
|
|
},
|
|
);
|
|
|
|
for my $Filename (@Attachments) {
|
|
|
|
# remove extra information from filename
|
|
$Filename =~ s{ \A Change / \d+ / }{}xms;
|
|
}
|
|
|
|
return @Attachments;
|
|
}
|
|
|
|
=head2 ChangeAttachmentExists()
|
|
|
|
Checks if a file with a given filename exists.
|
|
|
|
my $Exists = $ChangeObject->ChangeAttachmentExists(
|
|
Filename => 'test.txt',
|
|
ChangeID => 123,
|
|
UserID => 1,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ChangeAttachmentExists {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Needed (qw(Filename ChangeID 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 => 'Change/' . $Param{ChangeID} . '/' . $Param{Filename},
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub DESTROY {
|
|
my $Self = shift;
|
|
|
|
# execute all transaction events
|
|
$Self->EventHandlerTransaction();
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head1 PRIVATE INTERFACE
|
|
|
|
=head2 _CheckChangeStateIDs()
|
|
|
|
Check whether all of the given change state ids are valid.
|
|
|
|
my $Ok = $ChangeObject->_CheckChangeStateIDs(
|
|
ChangeStateIDs => [ 25, 26 ],
|
|
);
|
|
|
|
=cut
|
|
|
|
sub _CheckChangeStateIDs {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{ChangeStateIDs} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need ChangeStateIDs!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
if ( ref $Param{ChangeStateIDs} ne 'ARRAY' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'The param ChangeStateIDs must be an array reference!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check if ChangeStateIDs belong to correct general catalog class
|
|
for my $StateID ( @{ $Param{ChangeStateIDs} } ) {
|
|
my $State = $Self->ChangeStateLookup(
|
|
ChangeStateID => $StateID,
|
|
);
|
|
|
|
if ( !$State ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The state id $StateID is not valid!",
|
|
);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 _CheckChangeCIPIDs()
|
|
|
|
Check whether all of the given ids of category, impact or priority are valid.
|
|
|
|
my $Ok = $ChangeObject->_CheckChangeCIPIDs(
|
|
IDs => [ 25, 26 ], # mandatory
|
|
Type => 'Priority', # mandatory (Category|Impact|Priority)
|
|
);
|
|
|
|
=cut
|
|
|
|
sub _CheckChangeCIPIDs {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Needed (qw(IDs Type)) {
|
|
if ( !$Param{$Needed} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Needed!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check if IDs is an array reference
|
|
if ( ref $Param{IDs} ne 'ARRAY' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'The param IDs must be an array reference!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check Type param for valid values
|
|
if (
|
|
!$Param{Type}
|
|
|| ( $Param{Type} ne 'Category' && $Param{Type} ne 'Impact' && $Param{Type} ne 'Priority' )
|
|
)
|
|
{
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'The param Type must be either "Category" or "Impact" or "Priority"!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check if IDs belongs to correct general catalog class
|
|
for my $ID ( @{ $Param{IDs} } ) {
|
|
my $CIP = $Self->ChangeCIPLookup(
|
|
ID => $ID,
|
|
Type => $Param{Type},
|
|
);
|
|
|
|
if ( !$CIP ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The $Param{Type} id $ID is not valid!",
|
|
);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 _CheckChangeParams()
|
|
|
|
Checks the params to ChangeAdd() and ChangeUpdate().
|
|
There are no required parameters.
|
|
|
|
my $Ok = $ChangeObject->_CheckChangeParams(
|
|
ChangeTitle => 'Replacement of mail server', # (optional)
|
|
Description => 'New mail server <b>is</b> faster', # (optional)
|
|
DescriptionPlain => 'New mail server is faster', # (optional)
|
|
Justification => 'Old mail server<b>too</b> slow', # (optional)
|
|
JustificationPlain => 'Old mail server too slow', # (optional)
|
|
ChangeStateID => 4, # (optional)
|
|
ChangeManagerID => 5, # (optional)
|
|
ChangeBuilderID => 6, # (optional)
|
|
CategoryID => 7, # (optional)
|
|
ImpactID => 8, # (optional)
|
|
PriorityID => 9, # (optional)
|
|
RequestedTime => '2009-10-23 08:57:12', # (optional)
|
|
CABAgents => [ 1, 2, 4 ], # UserIDs # (optional)
|
|
CABCustomers => [ 'tt', 'mm' ], # CustomerUserIDs # (optional)
|
|
DynamicField_X => 'Sun', # (optional)
|
|
DynamicField_Y => 'Earth', # (optional)
|
|
);
|
|
|
|
The ChangeStateID is checked for existence in the general catalog.
|
|
These string parameters have length constraints:
|
|
|
|
Parameter | max. length
|
|
--------------------+-----------------
|
|
ChangeTitle | 250 characters
|
|
Description | 1800000 characters
|
|
DescriptionPlain | 1800000 characters
|
|
Justification | 1800000 characters
|
|
JustificationPlain | 1800000 characters
|
|
DynamicField_X | 3800 characters
|
|
DynamicField_Y | 3800 characters
|
|
|
|
=cut
|
|
|
|
sub _CheckChangeParams {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check the string and id parameters
|
|
ARGUMENT:
|
|
for my $Argument (
|
|
qw(
|
|
ChangeTitle
|
|
Description
|
|
DescriptionPlain
|
|
Justification
|
|
JustificationPlain
|
|
ChangeManagerID
|
|
ChangeBuilderID
|
|
ChangeStateID
|
|
)
|
|
)
|
|
{
|
|
|
|
# params are not required
|
|
next ARGUMENT if !exists $Param{$Argument};
|
|
|
|
# check if param is not defined
|
|
if ( !defined $Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The parameter '$Argument' must be defined!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check if param is not a reference
|
|
if ( ref $Param{$Argument} ne '' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The parameter '$Argument' mustn't be a reference!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check the maximum length of title
|
|
if ( $Argument eq 'ChangeTitle' && length( $Param{$Argument} ) > 250 ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The parameter '$Argument' must be shorter than 250 characters!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check the maximum length of description and justification
|
|
if (
|
|
$Argument eq 'Description'
|
|
|| $Argument eq 'DescriptionPlain'
|
|
|| $Argument eq 'Justification'
|
|
|| $Argument eq 'JustificationPlain'
|
|
)
|
|
{
|
|
if ( length( $Param{$Argument} ) > 1800000 ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The parameter '$Argument' must be shorter than 1800000 characters!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
# check the change dynamic fields
|
|
KEY:
|
|
for my $Key ( sort keys %Param ) {
|
|
|
|
next KEY if $Key !~ m{ \A DynamicField_(.*) \z }xms;
|
|
|
|
# params are not required
|
|
next KEY if !exists $Param{$Key};
|
|
|
|
# check if param is not defined
|
|
if ( !defined $Param{$Key} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The parameter '$Key' must be defined!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check the maximum length of dynamic fields
|
|
if ( length( $Param{$Key} ) > 3800 ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The parameter '$Key' must be shorter than 3800 characters!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check if requested_time has correct format
|
|
if (
|
|
defined $Param{RequestedTime}
|
|
&& $Param{RequestedTime} !~ 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 format for RequestedTime!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check if given ChangeStateID is valid
|
|
if ( $Param{ChangeStateID} ) {
|
|
return if !$Self->_CheckChangeStateIDs(
|
|
ChangeStateIDs => [ $Param{ChangeStateID} ],
|
|
);
|
|
}
|
|
|
|
# check if given category, impact or priority ID is valid
|
|
for my $Type (qw(Category Impact Priority)) {
|
|
if ( defined $Param{"${Type}ID"} ) {
|
|
return if !$Self->_CheckChangeCIPIDs(
|
|
IDs => [ $Param{"${Type}ID"} ],
|
|
Type => $Type,
|
|
);
|
|
}
|
|
|
|
if ( defined $Param{$Type} ) {
|
|
return if !$Self->ChangeCIPLookup(
|
|
CIP => $Param{$Type},
|
|
Type => $Type,
|
|
);
|
|
}
|
|
}
|
|
|
|
# change manager and change builder must be agents
|
|
ARGUMENT:
|
|
for my $Argument (qw(ChangeManagerID ChangeBuilderID)) {
|
|
|
|
# params are not required
|
|
next ARGUMENT if !exists $Param{$Argument};
|
|
|
|
# get user data
|
|
my %UserData = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
|
|
UserID => $Param{$Argument},
|
|
Valid => 1,
|
|
);
|
|
|
|
if ( !$UserData{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The $Argument $Param{$Argument} is not a valid user id!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# CAB agents must be agents
|
|
if ( exists $Param{CABAgents} ) {
|
|
if ( ref $Param{CABAgents} ne 'ARRAY' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'The parameter CABAgents is not an ARRAY reference!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check users
|
|
for my $UserID ( @{ $Param{CABAgents} } ) {
|
|
|
|
# get user data
|
|
my %UserData = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
|
|
UserID => $UserID,
|
|
Valid => 1,
|
|
);
|
|
|
|
if ( !$UserData{UserID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The CABAgent $UserID is not a valid user id!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
# CAB customers must be customers
|
|
if ( exists $Param{CABCustomers} ) {
|
|
if ( ref $Param{CABCustomers} ne 'ARRAY' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'The parameter CABCustomers is not an ARRAY reference!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get the valid id for "valid"
|
|
my $ValidID = $Kernel::OM->Get('Kernel::System::Valid')->ValidLookup(
|
|
Valid => 'valid',
|
|
);
|
|
|
|
# check customer users
|
|
for my $CustomerUser ( @{ $Param{CABCustomers} } ) {
|
|
|
|
# get customer user data
|
|
my %CustomerUserData = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerUserDataGet(
|
|
User => $CustomerUser,
|
|
Valid => 1,
|
|
);
|
|
|
|
if ( $CustomerUserData{ValidID} ne $ValidID ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The CABCustomer $CustomerUser is not a valid customer!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|