# -- # 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::ProcessManagement::DB::Process; use strict; use warnings; use Kernel::System::ProcessManagement::DB::Entity; use Kernel::System::ProcessManagement::DB::Activity; use Kernel::System::ProcessManagement::DB::ActivityDialog; use Kernel::System::ProcessManagement::DB::Process::State; use Kernel::System::ProcessManagement::DB::Transition; use Kernel::System::ProcessManagement::DB::TransitionAction; use Kernel::System::VariableCheck qw(:all); our @ObjectDependencies = ( 'Kernel::Config', 'Kernel::Language', 'Kernel::System::Cache', 'Kernel::System::DB', 'Kernel::System::DynamicField', 'Kernel::System::Log', 'Kernel::System::Main', 'Kernel::System::User', 'Kernel::System::YAML', ); =head1 NAME Kernel::System::ProcessManagement::DB::Process =head1 DESCRIPTION Process Management DB Process backend =head1 PUBLIC INTERFACE =head2 new() Don't use the constructor directly, use the ObjectManager instead: my $ProcessObject = $Kernel::OM->Get('Kernel::System::ProcessManagement::DB::Process'); =cut sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {}; bless( $Self, $Type ); $Self->{EntityObject} = Kernel::System::ProcessManagement::DB::Entity->new(); $Self->{ActivityDialogObject} = Kernel::System::ProcessManagement::DB::ActivityDialog->new(); $Self->{ActivityObject} = Kernel::System::ProcessManagement::DB::Activity->new(); $Self->{StateObject} = Kernel::System::ProcessManagement::DB::Process::State->new(); $Self->{TransitionObject} = Kernel::System::ProcessManagement::DB::Transition->new(); $Self->{TransitionActionObject} = Kernel::System::ProcessManagement::DB::TransitionAction->new(); # get the cache TTL (in seconds) $Self->{CacheTTL} = int( $Kernel::OM->Get('Kernel::Config')->Get('Process::CacheTTL') || 3600 ); # set lower if database is case sensitive $Self->{Lower} = ''; if ( $Kernel::OM->Get('Kernel::System::DB')->GetDatabaseFunction('CaseSensitive') ) { $Self->{Lower} = 'LOWER'; } return $Self; } =head2 ProcessAdd() add new Process returns the id of the created process if success or undef otherwise my $ID = $ProcessObject->ProcessAdd( EntityID => 'P1' # mandatory, exportable unique identifier Name => 'NameOfProcess', # mandatory StateEntityID => 'S1', Layout => $LayoutHashRef, # mandatory, diagram objects positions to be stored in # YAML format Config => $ConfigHashRef, # mandatory, process configuration to be stored in YAML # format UserID => 123, # mandatory ); Returns: $ID = 567; =cut sub ProcessAdd { my ( $Self, %Param ) = @_; # check needed stuff for my $Key (qw(EntityID Name StateEntityID Layout Config UserID)) { if ( !$Param{$Key} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Key!", ); return; } } # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # check if EntityID already exists return if !$DBObject->Prepare( SQL => " SELECT id FROM pm_process WHERE $Self->{Lower}(entity_id) = $Self->{Lower}(?)", Bind => [ \$Param{EntityID} ], Limit => 1, ); my $EntityExists; while ( my @Data = $DBObject->FetchrowArray() ) { $EntityExists = 1; } if ($EntityExists) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "The EntityID:$Param{EntityID} already exists for a process!" ); return; } # check config valid format (at least it must contain the description) if ( !IsHashRefWithData( $Param{Config} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Config needs to be a valid Hash reference!", ); return; } if ( !$Param{Config}->{Description} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need Description in Config!", ); return; } # get yaml object my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML'); # dump layout and config as string my $Layout = $YAMLObject->Dump( Data => $Param{Layout} ); my $Config = $YAMLObject->Dump( Data => $Param{Config} ); # Make sure the resulting string has the UTF-8 flag. YAML only sets it if # part of the data already had it. utf8::upgrade($Layout); utf8::upgrade($Config); # SQL return if !$DBObject->Do( SQL => ' INSERT INTO pm_process ( entity_id, name, state_entity_id, layout, config, create_time, create_by, change_time, change_by ) VALUES (?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)', Bind => [ \$Param{EntityID}, \$Param{Name}, \$Param{StateEntityID}, \$Layout, \$Config, \$Param{UserID}, \$Param{UserID}, ], ); return if !$DBObject->Prepare( SQL => 'SELECT id FROM pm_process WHERE entity_id = ?', Bind => [ \$Param{EntityID} ], ); my $ID; while ( my @Row = $DBObject->FetchrowArray() ) { $ID = $Row[0]; } # delete cache $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( Type => 'ProcessManagement_Process', ); return if !$ID; return $ID; } =head2 ProcessDelete() delete a Process returns 1 if success or undef otherwise my $Success = $ProcessObject->ProcessDelete( ID => 123, UserID => 123, ); =cut sub ProcessDelete { my ( $Self, %Param ) = @_; # check needed stuff for my $Key (qw(ID UserID)) { if ( !$Param{$Key} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Key!" ); return; } } # check if exists my $Process = $Self->ProcessGet( ID => $Param{ID}, UserID => 1, ); return if !IsHashRefWithData($Process); # delete process return if !$Kernel::OM->Get('Kernel::System::DB')->Do( SQL => 'DELETE FROM pm_process WHERE id = ?', Bind => [ \$Param{ID} ], ); # delete cache $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( Type => 'ProcessManagement_Process', ); return 1; } =head2 ProcessGet() get Process attributes my $Process = $ProcessObject->ProcessGet( ID => 123, # ID or EntityID is needed EntityID => 'P1', ActivityNames => 1, # default 0, 1 || 0, if 0 returns an Activities array # with the activity entity IDs, if 1 returns an # Activities hash with the activity entity IDs as # keys and Activity Names as values TransitionNames => 1, # default 0, 1 || 0, if 0 returns an Transitions array # with the transition entity IDs, if 1 returns an # Transitions hash with the transition entity IDs as # keys and Transition Names as values TransitionActionNames => 1, # default 0, 1 || 0, if 0 returns an TransitionActions array # with the TransitionAction entity IDs, if 1 returns an # TransitionAction hash with the TransitionAction entity IDs as # keys and TransitionAction Names as values UserID => 123, # mandatory ); Returns: $Process = { ID => 123, EntityID => 'P1', Name => 'some name', StateEntityID => 'S1', State => 'Active', Layout => $LayoutHashRef, Config => $ConfigHashRef, Activities => ['A1','A2','A3'], Activities => ['T1','T2','T3'], CreateTime => '2012-07-04 15:08:00', ChangeTime => '2012-07-04 15:08:00', }; $Process = { ID => 123, EntityID => 'P1', Name => 'some name', StateEntityID => 'S1', State => 'Active', Layout => $LayoutHashRef, Config => $ConfigHashRef, Activities => { 'A1' => 'Activity1', 'A2' => 'Activity2', 'A3' => 'Activity3', }; Transitions => { 'T1' => 'Transition1', 'T2' => 'Transition2', 'T3' => 'Transition3', }; TransitionActions => { 'TA1' => 'TransitionAction1', 'TA2' => 'TransitionAction2', 'TA3' => 'TransitionAction3', }; CreateTime => '2012-07-04 15:08:00', ChangeTime => '2012-07-04 15:08:00', }; =cut sub ProcessGet { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{ID} && !$Param{EntityID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need ID or EntityID!' ); return; } if ( !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need UserID!', ); return; } my $ActivityNames = 0; if ( defined $Param{ActivityNames} && $Param{ActivityNames} == 1 ) { $ActivityNames = 1; } my $TransitionNames = 0; if ( defined $Param{TransitionNames} && $Param{TransitionNames} == 1 ) { $TransitionNames = 1; } my $TransitionActionNames = 0; if ( defined $Param{TransitionActionNames} && $Param{TransitionActionNames} == 1 ) { $TransitionActionNames = 1; } # check cache my $CacheKey; if ( $Param{ID} ) { $CacheKey = 'ProcessGet::ID::' . $Param{ID} . '::ActivityNames::' . $ActivityNames . '::TransitionNames::' . $TransitionNames . '::TransitionActionNames::' . $TransitionActionNames; } else { $CacheKey = 'ProcessGet::EntityID::' . $Param{EntityID} . '::ActivityNames::' . $ActivityNames . '::TransitionNames::' . $TransitionNames . '::TransitionActionNames::' . $TransitionActionNames; } # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); my $Cache = $CacheObject->Get( Type => 'ProcessManagement_Process', Key => $CacheKey, ); return $Cache if $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # SQL if ( $Param{ID} ) { return if !$DBObject->Prepare( SQL => ' SELECT id, entity_id, name, state_entity_id, layout, config, create_time, change_time FROM pm_process WHERE id = ?', Bind => [ \$Param{ID} ], Limit => 1, ); } else { return if !$DBObject->Prepare( SQL => ' SELECT id, entity_id, name, state_entity_id, layout, config, create_time, change_time FROM pm_process WHERE entity_id = ?', Bind => [ \$Param{EntityID} ], Limit => 1, ); } # get yaml object my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML'); my %Data; while ( my @Data = $DBObject->FetchrowArray() ) { my $Layout = $YAMLObject->Load( Data => $Data[4] ); my $Config = $YAMLObject->Load( Data => $Data[5] ); %Data = ( ID => $Data[0], EntityID => $Data[1], Name => $Data[2], StateEntityID => $Data[3], Layout => $Layout, Config => $Config, CreateTime => $Data[6], ChangeTime => $Data[7], ); } return if !$Data{ID}; # create the ActivityList if ($ActivityNames) { my %Activities; if ( IsHashRefWithData( $Data{Config}->{Path} ) ) { my $ActivityList = $Self->{ActivityObject}->ActivityList( UseEntities => 1, UserID => 1, ); for my $ActivityEntityID ( sort keys %{ $Data{Config}->{Path} } ) { $Activities{$ActivityEntityID} = $ActivityList->{$ActivityEntityID}; } } $Data{Activities} = \%Activities; } else { my @Activities; if ( IsHashRefWithData( $Data{Config}->{Path} ) ) { # get path keys (ActivityEntityIDs) and map them into an array @Activities = map {$_} sort keys %{ $Data{Config}->{Path} }; } $Data{Activities} = \@Activities; } # create the transition list if ($TransitionNames) { my %Transitions; if ( IsHashRefWithData( $Data{Config}->{Path} ) ) { my $TransitionList = $Self->{TransitionObject}->TransitionList( UseEntities => 1, UserID => 1, ); for my $ActivityEntityID ( sort keys %{ $Data{Config}->{Path} } ) { for my $TransitionEntityID ( sort keys %{ $Data{Config}->{Path}->{$ActivityEntityID} } ) { $Transitions{$TransitionEntityID} = $TransitionList->{$TransitionEntityID}; } } } $Data{Transitions} = \%Transitions; } else { my @Transitions; if ( IsHashRefWithData( $Data{Config}->{Path} ) ) { for my $ActivityEntityID ( sort keys %{ $Data{Config}->{Path} } ) { for my $TransitionEntityID ( sort keys %{ $Data{Config}->{Path}->{$ActivityEntityID} } ) { push @Transitions, $TransitionEntityID; } } } $Data{Transitions} = \@Transitions; } # create the transition action list if ($TransitionActionNames) { my %TransitionActions; if ( IsHashRefWithData( $Data{Config}->{Path} ) ) { my $TransitionActionList = $Self->{TransitionActionObject}->TransitionActionList( UseEntities => 1, UserID => 1, ); for my $ActivityEntityID ( sort keys %{ $Data{Config}->{Path} } ) { my $TransitionPath = $Data{Config}->{Path}->{$ActivityEntityID}; for my $TransitionEntityID ( sort keys %{$TransitionPath} ) { my $TransitionActionPath = $Data{Config}->{Path}->{$ActivityEntityID}->{$TransitionEntityID} ->{Action}; if ( $TransitionActionPath && @{$TransitionActionPath} ) { for my $TransitionActionEntityID ( sort @{$TransitionActionPath} ) { $TransitionActions{$TransitionActionEntityID} = $TransitionActionList->{$TransitionEntityID}; } } } } } $Data{TransitionActions} = \%TransitionActions; } else { my @TransitionActions; if ( IsHashRefWithData( $Data{Config}->{Path} ) ) { for my $ActivityEntityID ( sort keys %{ $Data{Config}->{Path} } ) { my $TransitionPath = $Data{Config}->{Path}->{$ActivityEntityID}; for my $TransitionEntityID ( sort keys %{$TransitionPath} ) { my $TransitionActionPath = $TransitionPath->{$TransitionEntityID}->{TransitionAction}; if ( $TransitionActionPath && @{$TransitionActionPath} ) { for my $TransitionActionEntityID ( sort @{$TransitionActionPath} ) { push @TransitionActions, $TransitionActionEntityID; } } } } } $Data{TransitionActions} = \@TransitionActions; } $Data{State} = $Self->{StateObject}->StateLookup( EntityID => $Data{StateEntityID}, UserID => 1, ); # set cache $CacheObject->Set( Type => 'ProcessManagement_Process', Key => $CacheKey, Value => \%Data, TTL => $Self->{CacheTTL}, ); return \%Data; } =head2 ProcessUpdate() update Process attributes returns 1 if success or undef otherwise my $Success = $ProcessObject->ProcessUpdate( ID => 123, # mandatory EntityID => 'P1' # mandatory, exportable unique identifier Name => 'NameOfProcess', # mandatory StateentityID => 'S1', Layout => $LayoutHashRef, # mandatory, diagram objects positions to be stored in # YAML format Config => $ConfigHashRef, # mandatory, process configuration to be stored in YAML # format UserID => 123, # mandatory ); =cut sub ProcessUpdate { my ( $Self, %Param ) = @_; # check needed stuff for my $Key (qw(ID EntityID Name StateEntityID Layout Config UserID)) { if ( !$Param{$Key} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Key!" ); return; } } # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # check if EntityID already exists return if !$DBObject->Prepare( SQL => " SELECT id FROM pm_process WHERE $Self->{Lower}(entity_id) = $Self->{Lower}(?) AND id != ?", Bind => [ \$Param{EntityID}, \$Param{ID} ], LIMIT => 1, ); my $EntityExists; while ( my @Data = $DBObject->FetchrowArray() ) { $EntityExists = 1; } if ($EntityExists) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "The EntityID:$Param{Name} already exists for a process!", ); return; } # check config valid format (at least it must contain the description) if ( !IsHashRefWithData( $Param{Config} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Config needs to be a valid Hash reference!", ); return; } if ( !$Param{Config}->{Description} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need Description in Config!", ); return; } # get yaml object my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML'); # dump layout and config as string my $Layout = $YAMLObject->Dump( Data => $Param{Layout} ); my $Config = $YAMLObject->Dump( Data => $Param{Config} ); # Make sure the resulting string has the UTF-8 flag. YAML only sets it if # part of the data already had it. utf8::upgrade($Layout); utf8::upgrade($Config); # check if need to update db return if !$DBObject->Prepare( SQL => ' SELECT entity_id, name, state_entity_id, layout, config FROM pm_process WHERE id = ?', Bind => [ \$Param{ID} ], Limit => 1, ); my $CurrentEntityID; my $CurrentName; my $CurrentStateEntityID; my $CurrentLayout; my $CurrentConfig; while ( my @Data = $DBObject->FetchrowArray() ) { $CurrentEntityID = $Data[0]; $CurrentName = $Data[1]; $CurrentStateEntityID = $Data[2]; $CurrentLayout = $Data[3]; $CurrentConfig = $Data[4]; } if ($CurrentEntityID) { return 1 if $CurrentEntityID eq $Param{EntityID} && $CurrentName eq $Param{Name} && $CurrentStateEntityID eq $Param{StateEntityID} && $CurrentLayout eq $Layout && $CurrentConfig eq $Config; } # SQL return if !$DBObject->Do( SQL => ' UPDATE pm_process SET entity_id = ?, name = ?, state_entity_id = ?, layout = ?, config = ?, change_time = current_timestamp, change_by = ? WHERE id = ?', Bind => [ \$Param{EntityID}, \$Param{Name}, \$Param{StateEntityID}, \$Layout, \$Config, \$Param{UserID}, \$Param{ID}, ], ); # delete cache $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( Type => 'ProcessManagement_Process', ); return 1; } =head2 ProcessList() get a Process list my $List = $ProcessObject->ProcessList( UseEntities => 0, # default 0, 1 || 0. if 0 the return hash keys are # the process IDs otherwise keys are the # process entity IDs StateEntityIDs => ['S1','S2'], # optional, to filter processes that match listed # state entity IDs UserID => 1, ); Returns: $List = { 1 => 'NameOfProcess', } or $List = { 'P1' => 'NameOfProcess', } =cut sub ProcessList { my ( $Self, %Param ) = @_; # check needed if ( !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need UserID!" ); return; } my $StateEntityIDsStrg; if ( !IsArrayRefWithData( $Param{StateEntityIDs} ) ) { $StateEntityIDsStrg = 'ALL'; } else { $StateEntityIDsStrg = join ',', @{ $Param{StateEntityIDs} }; } # check cache my $UseEntities = 0; if ( defined $Param{UseEntities} && $Param{UseEntities} ) { $UseEntities = 1; } # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); my $CacheKey = 'ProcessList::UseEntities::' . $UseEntities . '::StateEntityIDs::' . $StateEntityIDsStrg; my $Cache = $CacheObject->Get( Type => 'ProcessManagement_Process', Key => $CacheKey, ); return $Cache if ref $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); my $SQL = ' SELECT id, entity_id, name FROM pm_process '; if ( $StateEntityIDsStrg ne 'ALL' ) { my $StateEntityIDsStrgDB = join ',', map { "'" . $DBObject->Quote($_) . "'" } @{ $Param{StateEntityIDs} }; $SQL .= "WHERE state_entity_id IN ($StateEntityIDsStrgDB)"; } return if !$DBObject->Prepare( SQL => $SQL ); my %Data; while ( my @Row = $DBObject->FetchrowArray() ) { if ( !$UseEntities ) { $Data{ $Row[0] } = $Row[2]; } else { $Data{ $Row[1] } = $Row[2]; } } # set cache $CacheObject->Set( Type => 'ProcessManagement_Process', Key => $CacheKey, Value => \%Data, TTL => $Self->{CacheTTL}, ); return \%Data; } =head2 ProcessListGet() get a Process list with all process details my $List = $ProcessObject->ProcessListGet( UserID => 1, ); Returns: $List = [ { ID => 123, EntityID => 'P1', Name => 'some name', StateEntityID => 'S1', State => 'Active', Layout => $LayoutHashRef, Config => $ConfigHashRef, Activities => ['A1','A2','A3'], CreateTime => '2012-07-04 15:08:00', ChangeTime => '2012-07-04 15:08:00', }, { ID => 456, EntityID => 'P2', Name => 'some name', StateEntityID => 'S1', State => 'Active', Layout => $LayoutHashRef, Config => $ConfigHashRef, Activities => ['A3','A4','A5'], CreateTime => '2012-07-04 15:10:00', ChangeTime => '2012-07-04 15:10:00', }, ]; =cut sub ProcessListGet { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need UserID!', ); return; } # get cache object my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache'); # check cache my $CacheKey = 'ProcessListGet'; my $Cache = $CacheObject->Get( Type => 'ProcessManagement_Process', Key => $CacheKey, ); return $Cache if $Cache; # get database object my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); # SQL return if !$DBObject->Prepare( SQL => ' SELECT id, entity_id FROM pm_process ORDER BY id', ); my @ProcessIDs; while ( my @Row = $DBObject->FetchrowArray() ) { push @ProcessIDs, $Row[0]; } my @Data; for my $ItemID (@ProcessIDs) { my $ProcessData = $Self->ProcessGet( ID => $ItemID, UserID => 1, ); push @Data, $ProcessData; } # set cache $CacheObject->Set( Type => 'ProcessManagement_Process', Key => $CacheKey, Value => \@Data, TTL => $Self->{CacheTTL}, ); return \@Data; } =head2 ProcessSearch() search processes by process name my $ProcessEntityIDs = $ProcessObject->ProcessSearch( ProcessName => 'SomeText', # e. g. "SomeText*", "Some*ext" or ['*SomeTest1*', '*SomeTest2*'] ); Returns: $ProcessEntityIDs = [ 'Process-e11e2e9aa83344a235279d4f6babc6ec', 'Process-f8194a25ab0ccddefeb4240c281c1f56' ]; =cut sub ProcessSearch { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{ProcessName} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need ProcessName!', ); return; } my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); my $SQL = 'SELECT DISTINCT entity_id FROM pm_process '; # if it's no ref, put it to array ref if ( ref $Param{ProcessName} eq '' ) { $Param{ProcessName} = [ $Param{ProcessName} ]; } if ( IsArrayRefWithData( $Param{ProcessName} ) ) { $SQL .= ' WHERE' if IsArrayRefWithData( $Param{ProcessName} ); } my @QuotedSearch; my $SQLOR = 0; VALUE: for my $Value ( @{ $Param{ProcessName} } ) { next VALUE if !defined $Value || !length $Value; $Value = '%' . $DBObject->Quote( $Value, 'Like' ) . '%'; $Value =~ s/\*/%/g; $Value =~ s/%%/%/gi; if ($SQLOR) { $SQL .= ' OR'; } $SQL .= ' name LIKE ? '; push @QuotedSearch, $Value; $SQLOR = 1; } if ( IsArrayRefWithData( $Param{ProcessName} ) ) { $SQL .= $DBObject->GetDatabaseFunction('LikeEscapeString'); } $SQL .= ' ORDER BY entity_id'; return if !$DBObject->Prepare( SQL => $SQL, Bind => [ \(@QuotedSearch) ] ); my @Data; while ( my @Row = $DBObject->FetchrowArray() ) { push @Data, $Row[0]; } return \@Data; } =head2 ProcessDump() gets a complete processes information dump from the DB including: Process State, Activities, ActivityDialogs, Transitions and TransitionActions my $ProcessDump = $ProcessObject->ProcessDump( ResultType => 'SCALAR' # 'SCALAR' || 'HASH' || 'FILE' Location => '/opt/otrs/var/myfile.txt' # mandatory for ResultType = 'FILE' UserID => 1, ); Returns: $ProcessDump = ' $Self->{'Process'} = { 'P1' => { 'Name' => 'Process 1', 'CreateTime' => '2012-07-21 08:11:33', 'ChangeTime' => '2012-07-21 08:11:33', 'Path' => { 'A1' => { 'T1' => { 'Action' => [ 'TA1', ], } }, 'StartActivity' => 'A1', 'StartActivityDialog' => 'AD1', 'State' => 'S1' }, # ... }; $Self->{'Process::State'} = { 'S1' => 'Active', 'S2' => 'Inactive', 'S3' => 'FadeAway' }; $Self->{'Process::Activity'} = { 'A1' => { 'Name' => 'Activity 1' 'CreateTime' => '2012-07-21 08:11:33', 'ChangeTime' => '2012-07-21 08:11:33', 'ActivityDialog' => { '1' => 'AD1', } }, }, # ... }; $Self->{'Process::ActivityDialog'} = { 'AD1' => { 'Name' => 'Activity Dialog 1', 'CreateTime' => '2012-07-21 08:11:33', 'ChangeTime' => '2012-07-21 08:11:33', 'DescriptionLong' => 'Longer description', 'DescriptionShort' => 'Short description', 'FieldOrder' => [ 'StateID', 'DynamicField_Marke', ], 'Fields' => { 'StateID' => { 'DefaultValue' => '1', 'DescriptionLong' => 'Longer description', 'DescriptionShort' => 'Short description', 'Display' => '0' }, 'DynamicField_Marke' => { 'DescriptionLong' => 'Longer description', 'DescriptionShort' => 'Short description', 'Display' => '2' }, }, #... }; $Self->{'Process::Transition'} = { 'T1' => { 'Name' => 'Transition 1' 'ChangeTime' => '2012-07-21 08:11:33', 'CreateTime' => '2012-07-21 08:11:33', 'Condition' => { 'Type' => 'and', 'Cond1' => { 'Fields' => { 'DynamicField_Marke' => { 'Match' => 'Teststring', 'Type' => 'String', }, }, 'Type' => 'and', }, }, }, # ... }; $Self->{'Process::Action'} = { 'TA1' => { 'Name' => 'Queue Move', 'CreateTime' => '2012-07-21 08:11:33', 'ChangeTime' => '2012-07-21 08:11:33', 'Module' => 'Kernel::System::Process::Transition::Action::QueueMove', 'Config' => { 'NewOwner' => 'root@localhost', 'TargetQueue' => 'Raw', }, }, # ... }; '; my $ProcessDump = $ProcessObject->ProcessDump( ResultType => 'HASH' # 'SCALAR' || 'HASH' || 'FILE' Location => '/opt/otrs/var/myfile.txt' # mandatory for ResultType = 'FILE' UserID => 1, ); Returns: $ProcessDump = { Process => { 'P1' => { 'Name' => 'Process 1', 'CreateTime' => '2012-07-21 08:11:33', 'ChangeTime' => '2012-07-21 08:11:33', 'Path' => { 'A1' => { 'T1' => { 'Action' => [ 'TA1', ], } }, 'StartActivity' => 'A1', 'StartActivityDialog' => 'AD1', 'State' => 'S1' }, # ... }; State => { 'S1' => 'Active', 'S2' => 'Inactive', 'S3' => 'FadeAway' }; Activity => { 'A1' => { 'Name' => 'Activity 1' 'CreateTime' => '2012-07-21 08:11:33', 'ChangeTime' => '2012-07-21 08:11:33', 'ActivityDialog' => { '1' => 'AD1', } }, }, # ... }; ActivityDialog => { 'AD1' => { 'Name' => 'Activity Dialog 1', 'CreateTime' => '2012-07-21 08:11:33', 'ChangeTime' => '2012-07-21 08:11:33', 'DescriptionLong' => 'Longer description', 'DescriptionShort' => 'Short description', 'FieldOrder' => [ 'StateID', 'DynamicField_Marke', ], 'Fields' => { 'StateID' => { 'DefaultValue' => '1', 'DescriptionLong' => 'Longer description', 'DescriptionShort' => 'Short description', 'Display' => '0' }, 'DynamicField_Marke' => { 'DescriptionLong' => 'Longer description', 'DescriptionShort' => 'Short description', 'Display' => '2' }, }, #... }; Transition => { 'T1' => { 'Name' => 'Transition 1' 'ChangeTime' => '2012-07-21 08:11:33', 'CreateTime' => '2012-07-21 08:11:33', 'Condition' => { 'Type' => 'and', 'Cond1' => { 'Fields' => { 'DynamicField_Marke' => { 'Match' => 'Teststring', 'Type' => 'String', }, }, 'Type' => 'and', }, }, }, # ... }; TransitionAction => { 'TA1' => { 'Name' => 'Queue Move', 'CreateTime' => '2012-07-21 08:11:33', 'ChangeTime' => '2012-07-21 08:11:33', 'Module' => 'Kernel::System::Process::Transition::Action::QueueMove', 'Config' => { 'NewOwner' => 'root@localhost', 'TargetQueue' => 'Raw', }, }, # ... }; } my $ProcessDump = $ProcessObject->ProcessDump( ResultType => 'Location' # 'SCALAR' || 'HASH' || 'FILE' Location => '/opt/otrs/var/myfile.txt' # mandatory for ResultType = 'FILE' UserID => 1, ); Returns: $ProcessDump = '/opt/otrs/var/myfile.txt'; # or undef if can't write the file =cut sub ProcessDump { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need UserID!', ); return; } if ( !defined $Param{ResultType} ) { $Param{ResultType} = 'SCALAR'; } if ( $Param{ResultType} eq 'FILE' ) { if ( !$Param{Location} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need Location for ResultType \'FILE\'!', ); } } # get States my %StateDump = %{ $Self->{StateObject}->StateList( UserID => 1 ) }; # get Processes my $ProcessList = $Self->ProcessListGet( UserID => 1 ); my %ProcessDump; PROCESS: for my $ProcessData ( @{$ProcessList} ) { next PROCESS if !IsHashRefWithData($ProcessData); $ProcessDump{ $ProcessData->{EntityID} } = { Name => $ProcessData->{Name}, CreateTime => $ProcessData->{CreateTime}, ChangeTime => $ProcessData->{ChangeTime}, StateEntityID => $ProcessData->{StateEntityID}, State => $ProcessData->{State}, StartActivity => $ProcessData->{Config}->{StartActivity} || '', StartActivityDialog => $ProcessData->{Config}->{StartActivityDialog} || '', Path => $ProcessData->{Config}->{Path} || {}, }; } # get Activities my $ActivitiesList = $Self->{ActivityObject}->ActivityListGet( UserID => 1 ); my %ActivityDump; ACTIVITY: for my $ActivityData ( @{$ActivitiesList} ) { next ACTIVITY if !IsHashRefWithData($ActivityData); $ActivityDump{ $ActivityData->{EntityID} } = { ID => $ActivityData->{ID}, Name => $ActivityData->{Name}, CreateTime => $ActivityData->{CreateTime}, ChangeTime => $ActivityData->{ChangeTime}, ActivityDialog => $ActivityData->{Config}->{ActivityDialog} || '', }; } # get ActivityDialogs my $ActivityDialogsList = $Self->{ActivityDialogObject}->ActivityDialogListGet( UserID => 1 ); my %ActivityDialogDump; ACTIVITYDIALOG: for my $ActivityDialogData ( @{$ActivityDialogsList} ) { next ACTIVITY if !IsHashRefWithData($ActivityDialogData); $ActivityDialogDump{ $ActivityDialogData->{EntityID} } = { Name => $ActivityDialogData->{Name}, CreateTime => $ActivityDialogData->{CreateTime}, ChangeTime => $ActivityDialogData->{ChangeTime}, Interface => $ActivityDialogData->{Config}->{Interface} || '', DescriptionShort => $ActivityDialogData->{Config}->{DescriptionShort} || '', DescriptionLong => $ActivityDialogData->{Config}->{DescriptionLong} || '', Fields => $ActivityDialogData->{Config}->{Fields} || {}, FieldOrder => $ActivityDialogData->{Config}->{FieldOrder} || [], Permission => $ActivityDialogData->{Config}->{Permission} || '', RequiredLock => $ActivityDialogData->{Config}->{RequiredLock} || '', SubmitAdviceText => $ActivityDialogData->{Config}->{SubmitAdviceText} || '', SubmitButtonText => $ActivityDialogData->{Config}->{SubmitButtonText} || '', }; } # get Transitions my $TransitionsList = $Self->{TransitionObject}->TransitionListGet( UserID => 1 ); my %TransitionDump; TRANSITION: for my $TransitionData ( @{$TransitionsList} ) { next TRANSITION if !IsHashRefWithData($TransitionData); $TransitionDump{ $TransitionData->{EntityID} } = { Name => $TransitionData->{Name}, CreateTime => $TransitionData->{CreateTime}, ChangeTime => $TransitionData->{ChangeTime}, Condition => $TransitionData->{Config}->{Condition} || {}, ConditionLinking => $TransitionData->{Config}->{ConditionLinking} || '', }; } # get TransitionActions my $TransitionActionsList = $Self->{TransitionActionObject}->TransitionActionListGet( UserID => 1 ); my %TransitionActionDump; TRANSITIONACTION: for my $TransitionActionData ( @{$TransitionActionsList} ) { next TRANSITIONACTION if !IsHashRefWithData($TransitionActionData); $TransitionActionDump{ $TransitionActionData->{EntityID} } = { Name => $TransitionActionData->{Name}, CreateTime => $TransitionActionData->{CreateTime}, ChangeTime => $TransitionActionData->{ChangeTime}, Module => $TransitionActionData->{Config}->{Module} || '', Config => $TransitionActionData->{Config}->{Config} || {}, }; } # delete cache (this will also delete the cache for display or hide AgentTicketProcess menu # item) $Kernel::OM->Get('Kernel::System::Cache')->CleanUp( Type => 'ProcessManagement_Process', ); # return Hash useful for JSON if ( $Param{ResultType} eq 'HASH' ) { return { 'Process' => \%ProcessDump, 'State' => \%StateDump, 'Activity' => \%ActivityDump, 'ActivityDialog' => \%ActivityDialogDump, 'Transition' => \%TransitionDump, 'TransitionAction' => \%TransitionActionDump, }; } else { # create output my $Output = $Self->_ProcessItemOutput( Key => "Process", Value => \%ProcessDump, ); $Output .= $Self->_ProcessItemOutput( Key => 'Process::State', Value => \%StateDump, ); $Output .= $Self->_ProcessItemOutput( Key => 'Process::Activity', Value => \%ActivityDump, ); $Output .= $Self->_ProcessItemOutput( Key => 'Process::ActivityDialog', Value => \%ActivityDialogDump, ); $Output .= $Self->_ProcessItemOutput( Key => 'Process::Transition', Value => \%TransitionDump, ); $Output .= $Self->_ProcessItemOutput( Key => 'Process::TransitionAction', Value => \%TransitionActionDump, ); # return a scalar variable with all config as test if ( $Param{ResultType} ne 'FILE' ) { return $Output; } # return a file location else { # get user data of the current user to use for the file comment my %User = $Kernel::OM->Get('Kernel::System::User')->GetUserData( UserID => $Param{UserID}, ); # remove home from location path to show in file comment my $Home = $Kernel::OM->Get('Kernel::Config')->Get('Home'); my $Location = $Param{Location}; $Location =~ s{$Home\/}{}xmsg; # build comment (therefore we need to trick out the filter) my $FileStart = <<'EOF'; # OTRS config file (automatically generated) # VERSION:1.1 package Kernel::Config::Files::ZZZProcessManagement; use strict; use warnings; no warnings 'redefine'; ## no critic use utf8; sub Load { my ($File, $Self) = @_; EOF my $FileEnd = <<'EOF'; return; } 1; EOF $Output = $FileStart . $Output . $FileEnd; my $FileLocation = $Kernel::OM->Get('Kernel::System::Main')->FileWrite( Location => $Param{Location}, Content => \$Output, Mode => 'utf8', Type => 'Local', ); return $FileLocation; } } } =head2 ProcessImport() import a process YAML file/content my %ProcessImport = $ProcessObject->ProcessImport( Content => $YAMLContent, # mandatory, YAML format OverwriteExistingEntities => 0, # 0 || 1 UserID => 1, # mandatory ); Returns: %ProcessImport = ( Message => 'The Message to show.', # error or success message Comment => 'Any comment', # optional Success => 1, # 1 if success or undef otherwise ); =cut sub ProcessImport { my ( $Self, %Param ) = @_; for my $Needed (qw(Content UserID)) { # check needed stuff if ( !$Param{$Needed} ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } my $ProcessData = $Kernel::OM->Get('Kernel::System::YAML')->Load( Data => $Param{Content} ); if ( ref $ProcessData ne 'HASH' ) { return ( Message => "Couldn't read process configuration file. Please make sure you file is valid.", ); } # collect all used fields and make sure they're present my @UsedDynamicFields; for my $ActivityDialog ( sort keys %{ $ProcessData->{ActivityDialogs} } ) { for my $FieldName ( sort keys %{ $ProcessData->{ActivityDialogs}->{$ActivityDialog}->{Config}->{Fields} } ) { if ( $FieldName =~ s{DynamicField_(\w+)}{$1}xms ) { push @UsedDynamicFields, $FieldName; } } } # get dynamic field object my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); # get all present dynamic fields and check if the fields used in the config are beyond them my $DynamicFieldList = $DynamicFieldObject->DynamicFieldList( ResultType => 'HASH', ); my @PresentDynamicFieldNames = values %{$DynamicFieldList}; my @MissingDynamicFieldNames; for my $UsedDynamicFieldName (@UsedDynamicFields) { if ( !grep { $_ eq $UsedDynamicFieldName } @PresentDynamicFieldNames ) { push @MissingDynamicFieldNames, $UsedDynamicFieldName; } } if ( $#MissingDynamicFieldNames > -1 ) { my $MissingDynamicFields = join( ', ', @MissingDynamicFieldNames ); return ( Message => "The following dynamic fields are missing: $MissingDynamicFields. " . "Import has been stopped.", ); } # make sure all activities and dialogs are present my @UsedActivityDialogs; for my $ActivityEntityID ( @{ $ProcessData->{Process}->{Activities} } ) { if ( ref $ProcessData->{Activities}->{$ActivityEntityID} ne 'HASH' ) { return ( Message => "Missing data for Activity $ActivityEntityID.", ); } else { for my $UsedActivityDialog ( @{ $ProcessData->{Activities}->{$ActivityEntityID}->{ActivityDialogs} } ) { push @UsedActivityDialogs, $UsedActivityDialog; } } } for my $ActivityDialogEntityID (@UsedActivityDialogs) { if ( ref $ProcessData->{ActivityDialogs}->{$ActivityDialogEntityID} ne 'HASH' ) { return ( Message => "Missing data for ActivityDialog $ActivityDialogEntityID.", ); } } # make sure all transitions are present for my $TransitionEntityID ( @{ $ProcessData->{Process}->{Transitions} } ) { if ( ref $ProcessData->{Transitions}->{$TransitionEntityID} ne 'HASH' ) { return ( Message => "Missing data for Transition $TransitionEntityID.", ); } } # make sure all transition actions are present for my $TransitionActionEntityID ( @{ $ProcessData->{Process}->{TransitionActions} } ) { if ( ref $ProcessData->{TransitionActions}->{$TransitionActionEntityID} ne 'HASH' ) { return ( Message => "Missing data for TransitionAction $TransitionActionEntityID.", ); } } my %EntityMapping; my %PartNameMap = ( Activity => 'Activities', ActivityDialog => 'ActivityDialogs', Transition => 'Transitions', TransitionAction => 'TransitionActions' ); # if OverwriteExistingEntities no new entities must be added to the DB unless the entity does # not not exists if ( $Param{OverwriteExistingEntities} ) { my $EntityID = $ProcessData->{Process}->{EntityID}; my $NewEntityID = $EntityID; # check if EntityID matched the format (it could be that a process from 3.3.x is been # imported) if ( $NewEntityID !~ m{\A Process - [0-9a-f]{32} \z}msx ) { # generate new EntityIDs $NewEntityID = $Self->{EntityObject}->EntityIDGenerate( EntityType => 'Process', UserID => $Param{UserID}, ); } $EntityMapping{Process}->{$EntityID} = $NewEntityID; for my $PartName (qw(Activity ActivityDialog Transition TransitionAction)) { for my $PartEntityID ( sort keys %{ $ProcessData->{ $PartNameMap{$PartName} } } ) { $NewEntityID = $PartEntityID; # check if EntityID matched the format (it could be that a process from 3.3.x is been # imported) if ( $NewEntityID !~ m{\A $PartName - [0-9a-f]{32} \z}msx ) { # generate new EntityIDs $NewEntityID = $Self->{EntityObject}->EntityIDGenerate( EntityType => $PartName, UserID => $Param{UserID}, ); } $EntityMapping{ $PartNameMap{$PartName} }->{$PartEntityID} = $NewEntityID; } # make sure that all entity mapping parts are defined as hash references $EntityMapping{ $PartNameMap{$PartName} } //= {}; } } else { my $EntityID = $ProcessData->{Process}->{EntityID}; # generate new EntityIDs my $NewEntityID = $Self->{EntityObject}->EntityIDGenerate( EntityType => 'Process', UserID => $Param{UserID}, ); $EntityMapping{Process}->{$EntityID} = $NewEntityID; for my $PartName (qw(Activity ActivityDialog Transition TransitionAction)) { for my $PartEntityID ( sort keys %{ $ProcessData->{ $PartNameMap{$PartName} } } ) { $NewEntityID = $Self->{EntityObject}->EntityIDGenerate( EntityType => $PartName, UserID => $Param{UserID}, ); $EntityMapping{ $PartNameMap{$PartName} }->{$PartEntityID} = $NewEntityID; } # make sure that all entity mapping parts are defined as hash references $EntityMapping{ $PartNameMap{$PartName} } //= {}; } # set EntityIDs my $UpdateResult = $Self->_ImportedEntitiesUpdate( ProcessData => $ProcessData, EntityMapping => \%EntityMapping, ); if ( !$UpdateResult->{Success} ) { return %{$UpdateResult}; } # overwrite process data with the temporary entities $ProcessData = $UpdateResult->{ProcessData}; } # invert the entity mappings, this is needed as we need to check if the new entities exists: # for non overwriting processes they must not exists and new records must be generated, # for overwriting processes it might happens that one record does not exists and it needs # to be created before it is updated # if new entities are to be created they will be using minimal data and updated with real data # later, this way overwriting and non overwriting processes will share the same logic %{ $EntityMapping{Process} } = reverse %{ $EntityMapping{Process} }; %{ $EntityMapping{Activities} } = reverse %{ $EntityMapping{Activities} }; %{ $EntityMapping{ActivityDialogs} } = reverse %{ $EntityMapping{ActivityDialogs} }; %{ $EntityMapping{Transitions} } = reverse %{ $EntityMapping{Transitions} }; %{ $EntityMapping{TransitionActions} } = reverse %{ $EntityMapping{TransitionActions} }; my %AddedEntityIDs; # get all processes my $ProcessList = $Self->ProcessList( UseEntities => 1, UserID => $Param{UserID}, ); # check if processes exists otherwise create them for my $ProcessEntityID ( sort keys %{ $EntityMapping{Process} } ) { if ( !$ProcessList->{$ProcessEntityID} ) { # create an empty process my $ProcessID = $Self->ProcessAdd( EntityID => $ProcessEntityID, Name => 'NewProcess', StateEntityID => 'S1', Layout => {}, Config => { Path => {}, Description => 'NewProcess', }, UserID => $Param{UserID}, ); if ( !$ProcessID ) { return $Self->_ProcessImportRollBack( AddedEntityIDs => \%AddedEntityIDs, UserID => $Param{UserID}, Message => 'Process ' . $ProcessData->{Process}->{Name} . ' could not be added. Stopping import!', ); } # remember added entity $AddedEntityIDs{Process}->{$ProcessEntityID} = $ProcessID; } } my %PartConfigMap = ( Activity => {}, ActivityDialog => { DescriptionShort => 'NewActivityDialog', Fields => {}, FieldOrder => [], }, Transition => { Condition => {}, }, TransitionAction => { Module => 'NewTransitionAction', Config => {}, }, ); # create missing process parts for my $PartName (qw(Activity ActivityDialog Transition TransitionAction)) { my $PartListFunction = $PartName . 'List'; my $PartAddFunction = $PartName . 'Add'; my $PartObject = $PartName . 'Object'; # get all part items my $PartsList = $Self->{$PartObject}->$PartListFunction( UseEntities => 1, UserID => $Param{UserID}, ); # check if part exists otherwise create them for my $PartEntityID ( sort keys %{ $EntityMapping{ $PartNameMap{$PartName} } } ) { if ( !$PartsList->{$PartEntityID} ) { # create an empty part my $PartID = $Self->{$PartObject}->$PartAddFunction( EntityID => $PartEntityID, Name => "New$PartName", Config => $PartConfigMap{$PartName}, UserID => $Param{UserID}, ); if ( !$PartID ) { return $Self->_ProcessImportRollBack( AddedEntityIDs => \%AddedEntityIDs, UserID => $Param{UserID}, Message => "$PartName " . $ProcessData->{ $PartNameMap{$PartName} }->{$PartEntityID}->{Name} . ' could not be added. Stopping import!', ); } # remember added entity $AddedEntityIDs{ $PartNameMap{$PartName} }->{$PartEntityID} = $PartID; } } } # update all entities with real data # update process for my $ProcessEntityID ( sort keys %{ $EntityMapping{Process} } ) { my $Process = $Self->ProcessGet( EntityID => $ProcessEntityID, UserID => $Param{UserID}, ); my $Success = $Self->ProcessUpdate( %{ $ProcessData->{Process} }, ID => $Process->{ID}, UserID => $Param{UserID}, ); if ( !$Success ) { return $Self->_ProcessImportRollBack( AddedEntityIDs => \%AddedEntityIDs, UserID => $Param{UserID}, Message => "Process: $ProcessEntityID could not be updated. " . "Stopping import!", ); } } # update all other process parts for my $PartName (qw(Activity ActivityDialog Transition TransitionAction)) { my $PartGetFunction = $PartName . 'Get'; my $PartUpdateFunction = $PartName . 'Update'; my $PartObject = $PartName . 'Object'; for my $PartEntityID ( sort keys %{ $EntityMapping{ $PartNameMap{$PartName} } } ) { my $Part = $Self->{$PartObject}->$PartGetFunction( EntityID => $PartEntityID, UserID => $Param{UserID} ); my $Success = $Self->{$PartObject}->$PartUpdateFunction( %{ $ProcessData->{ $PartNameMap{$PartName} }->{$PartEntityID} }, ID => $Part->{ID}, UserID => $Param{UserID}, ); if ( !$Success ) { return $Self->_ProcessImportRollBack( AddedEntityIDs => \%AddedEntityIDs, UserID => $Param{UserID}, Message => "$PartName: $PartEntityID could not be updated. " . " Stopping import!", ); } } } return ( Message => $Kernel::OM->Get('Kernel::Language')->Translate( 'The process "%s" and all of its data has been imported successfully.', $ProcessData->{Process}->{Name} ), Success => 1, ); } sub _ProcessItemOutput { my ( $Self, %Param ) = @_; my $Output = $Kernel::OM->Get('Kernel::System::Main')->Dump( $Param{Value}, ); my $Key = "\$Self->{'$Param{Key}'}"; $Output =~ s{\A \$VAR1}{$Key}mxs; return $Output . "\n"; } sub _ImportedEntitiesUpdate { my ( $Self, %Param ) = @_; my %EntityMapping = %{ $Param{EntityMapping} }; # re-write process with entity mapping information my $Process = $Param{ProcessData}->{Process}; my $NewProcess; # set non changing root attributes for my $Attribute (qw(Name StateEntityID)) { $NewProcess->{$Attribute} = $Process->{$Attribute}; } # set new process main entity my $NewProcessEntityID = $EntityMapping{Process}->{ $Process->{EntityID} }; if ( !$NewProcessEntityID ) { my $Message = "Could not find a entity mapping for Process: $Process->{EntityID}"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } $NewProcess->{EntityID} = $NewProcessEntityID; # set the process layout $NewProcess->{Layout} = {}; for my $ActivityEntityID ( sort keys %{ $Process->{Layout} } ) { my $NewActivityEntityID = $EntityMapping{Activities}->{$ActivityEntityID}; if ( !$NewActivityEntityID ) { my $Message = "Could not find a entity mapping for Activity: $ActivityEntityID"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } $NewProcess->{Layout}->{$NewActivityEntityID} = $Process->{Layout}->{$ActivityEntityID}; } # set process config non changing attributes $NewProcess->{Config}->{Description} = $Process->{Config}->{Description}; # set process config start activity and start activity dialog EntityID my %AttributeMap = ( Activity => 'Activities', ActivityDialog => 'ActivityDialogs', ); ATTRIBUTE: for my $Attribute (qw(Activity ActivityDialog)) { $NewProcess->{Config}->{"Start$Attribute"} = ''; next ATTRIBUTE if !$Process->{Config}->{"Start$Attribute"}; my $NewAttributeEntityID = $EntityMapping{ $AttributeMap{$Attribute} } ->{ $Process->{Config}->{"Start$Attribute"} }; if ( !$NewAttributeEntityID ) { my $Message = "Could not find a entity mapping for $Attribute: " . "$Process->{Config}->{Start$Attribute}"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } $NewProcess->{Config}->{"Start$Attribute"} = $NewAttributeEntityID; } # set process path $NewProcess->{Config}->{Path} = {}; for my $ActivityEntityID ( sort keys %{ $Process->{Config}->{Path} } ) { # set new activity EntityID in process path my $NewActivityEntityID = $EntityMapping{Activities}->{$ActivityEntityID}; if ( !$NewActivityEntityID ) { my $Message = "Could not find a entity mapping for Activity: $ActivityEntityID"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } $NewProcess->{Config}->{Path}->{$NewActivityEntityID} = {}; # check if original action has configuration (e.g. last activity might be empty) my $Activity = $Process->{Config}->{Path}->{$ActivityEntityID}; if ( IsHashRefWithData($Activity) ) { for my $TransitionEntityID ( sort keys %{$Activity} ) { my $Transition = $Activity->{$TransitionEntityID}; my $NewTransition; for my $TransitionActionEntityID ( @{ $Transition->{TransitionAction} } ) { # set new transition action EntityID from process path activity transition my $NewTransitionActionEntityID = $EntityMapping{TransitionActions}->{$TransitionActionEntityID}; if ( !$NewTransitionActionEntityID ) { my $Message = "Could not find a entity mapping for Transition Action: " . "$TransitionActionEntityID"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } push @{ $NewTransition->{TransitionAction} }, $NewTransitionActionEntityID; } # set new activity EntityID stored in the transition my $NewDestinationActivityEntityID = $EntityMapping{Activities}->{ $Transition->{ActivityEntityID} }; if ( !$NewDestinationActivityEntityID ) { my $Message = "Could not find a entity mapping for Activity: " . "$Transition->{ActivityEntityID}"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } $NewTransition->{ActivityEntityID} = $NewDestinationActivityEntityID; # set new transition EntityID my $NewTransitionEntityID = $EntityMapping{Transitions}->{$TransitionEntityID}; if ( !$NewTransitionEntityID ) { my $Message = "Could not find a entity mapping for Transition: $TransitionEntityID"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } # set new transition to its entity hash key $NewProcess->{Config}->{Path}->{$NewActivityEntityID}->{$NewTransitionEntityID} = $NewTransition; } } } # re-write activities with entity mapping information my $Activities = $Param{ProcessData}->{Activities}; my $NewActivities; for my $ActivityEntityID ( sort keys %{$Activities} ) { # get current old activity my $CurrentActivity = $Activities->{$ActivityEntityID}; # set new activity EntityID my $NewActivityEntityID = $EntityMapping{Activities}->{$ActivityEntityID}; if ( !$NewActivityEntityID ) { my $Message = "Could not find a entity mapping for Activity: $ActivityEntityID"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } $NewActivities->{$NewActivityEntityID}->{EntityID} = $NewActivityEntityID; # set non changing attributes $NewActivities->{$NewActivityEntityID}->{Name} = $CurrentActivity->{Name}; # set an empty configuration $NewActivities->{$NewActivityEntityID}->{Config}->{ActivityDialog} = {}; # set new entities for the configured activity dialogs my $CurrentActivityDialogs = $CurrentActivity->{Config}->{ActivityDialog}; for my $OrderKey ( sort keys %{$CurrentActivityDialogs} ) { # get old activity dialog EntityID my $ActivityDialogEntityID = $CurrentActivityDialogs->{$OrderKey}; # set new activity dialog EntityID my $NewActivityDialogEntityID = $EntityMapping{ActivityDialogs}->{$ActivityDialogEntityID}; if ( !$NewActivityDialogEntityID ) { my $Message = "Could not find a entity mapping for Activity Dialog: " . "$ActivityDialogEntityID"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } $NewActivities->{$NewActivityEntityID}->{Config}->{ActivityDialog}->{$OrderKey} = $NewActivityDialogEntityID; } } # re-write all other parts my %PartNameMap = ( ActivityDialog => 'ActivityDialogs', Transition => 'Transitions', TransitionAction => 'TransitionActions' ); my %NewParts; for my $PartName (qw(ActivityDialog Transition TransitionAction)) { my $CurrentParts = $Param{ProcessData}->{ $PartNameMap{$PartName} }; for my $CurrentEntityID ( sort keys %{$CurrentParts} ) { # get current old process part my $CurrentPart = $CurrentParts->{$CurrentEntityID}; # set new part EntityID my $NewEntityID = $EntityMapping{ $PartNameMap{$PartName} }->{$CurrentEntityID}; if ( !$NewEntityID ) { my $Message = "Could not find a entity mapping for $PartName: $CurrentEntityID"; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'Error', Message => $Message, ); return { Success => 0, Message => $Message, }; } $NewParts{ $PartNameMap{$PartName} }->{$NewEntityID}->{EntityID} = $NewEntityID; # set non changing attributes for my $Attribute (qw(Name Config)) { $NewParts{ $PartNameMap{$PartName} }->{$NewEntityID}->{$Attribute} = $CurrentPart->{$Attribute}; } } } return { Success => 1, ProcessData => { %NewParts, Process => $NewProcess, Activities => $NewActivities, }, }; } sub _ProcessImportRollBack { my ( $Self, %Param ) = @_; my %AddedEntityIDs = %{ $Param{AddedEntityIDs} }; my $Error; # delete added processes for my $ProcessEntityID ( sort keys %{ $AddedEntityIDs{Process} } ) { my $ProcessID = $AddedEntityIDs{Process}->{$ProcessEntityID}; my $Success = $Self->ProcessDelete( ID => $ProcessID, UserID => $Param{UserID}, ); if ( !$Success ) { $Error = 1; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Process: $ProcessEntityID could not be deleted", ); } } my %PartNameMap = ( Activity => 'Activities', ActivityDialog => 'ActivityDialogs', Transition => 'Transitions', TransitionAction => 'TransitionActions' ); # delete added process parts for my $Part (qw(Activity ActivityDialog Transition TransitionAction)) { for my $PartEntityID ( sort keys %{ $AddedEntityIDs{ $PartNameMap{$Part} } } ) { my $PartID = $AddedEntityIDs{ $PartNameMap{$Part} }->{$PartEntityID}; my $DeleteFunction = $Part . 'Delete'; my $Object = $Part . 'Object'; my $Success = $Self->{$Object}->$DeleteFunction( ID => $PartID, UserID => $Param{UserID}, ); if ( !$Success ) { $Error = 1; $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "$Part: $PartEntityID could not be deleted", ); } } } my $Comment = 'Process could not be imported. All changes have been rolled back.'; if ($Error) { $Comment = ' There was an error rolling back the partially imported process, please' . ' check the error log for details.'; } return ( Success => 0, Message => $Param{Message}, Comment => $Comment, ); } 1; =head1 TERMS AND CONDITIONS This software is part of the OTRS project (L). This software comes with ABSOLUTELY NO WARRANTY. For details, see the enclosed file COPYING for license information (GPL). If you did not receive this file, see L. =cut