-
Notifications
You must be signed in to change notification settings - Fork 243
Expand file tree
/
Copy pathProcessMapTrait.php
More file actions
186 lines (156 loc) · 6.58 KB
/
ProcessMapTrait.php
File metadata and controls
186 lines (156 loc) · 6.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
<?php
namespace ProcessMaker\Traits;
use Illuminate\Support\Collection;
use ProcessMaker\Bpmn\Process;
use ProcessMaker\Models\Process as ModelsProcess;
use ProcessMaker\Models\ProcessRequest;
use SimpleXMLElement;
trait ProcessMapTrait
{
/**
* Load XML from a string and register its namespaces.
* This function will help to prepare the XML for further processing.
*/
private function loadAndPrepareXML(string $bpmn): SimpleXMLElement
{
$xml = simplexml_load_string($bpmn);
$namespaces = $xml->getNamespaces(true);
// Register the BPMN namespace explicitly
$xml->registerXPathNamespace('bpmn', 'http://www.omg.org/spec/BPMN/20100524/MODEL');
foreach ($namespaces as $prefix => $ns) {
$xml->registerXPathNamespace($prefix, $ns);
}
return $xml;
}
/**
* Get the maximum token ID for a given element ID.
*/
private function getMaxTokenId(ProcessRequest $request, ?string $elementId): ?int
{
return $request->tokens()
->where('element_id', $elementId)
->max('id');
}
/**
* Get the token count for a given element ID.
*/
private function getTokenCount(ProcessRequest $request, string $elementId): int
{
return $request->tokens()
->where([
'element_id' => $elementId,
'process_request_id' => $request->id,
])
->count();
}
/**
* Filter the XML using the provided XPath query and return a Collection of string values.
*/
private function filterXML(SimpleXMLElement $xml, string $xpathQuery): Collection
{
$elements = $xml->xpath($xpathQuery);
return collect(array_map('strval', $elements));
}
/**
* Filter the XML to get IDs of all nodes excluding "lanes" and "pools" nodes.
*/
private function getNodeIds(SimpleXMLElement $xml): Collection
{
$query = '//*[name() != "bpmn:lane" and name() != "bpmn:participant"]/@id';
return $this->filterXML($xml, $query);
}
/**
* Performs an XPath query to get sequenceFlow elements
* whose 'sourceRef' attribute is in the string of completed nodes
* and 'targetRef' attribute is in the string of in-progress and completed nodes
* also validates Nodes in progress that were completed before to obtain their paths.
*/
private function getCompletedSequenceFlow(SimpleXMLElement $xml, string $completedNodesStr, string $inProgressNodesStr, string $completedInProgressNode): Collection
{
$inProgressAndCompletedNodes = $completedNodesStr . ' ' . $inProgressNodesStr;
$query = '//bpmn:sequenceFlow[contains("' . $completedNodesStr . '", @sourceRef) and contains("' . $inProgressAndCompletedNodes . '", @targetRef)]/@id';
$query = $query . ' | //bpmn:sequenceFlow[contains("' . $completedInProgressNode . '", @sourceRef) and contains("' . $inProgressAndCompletedNodes . '", @targetRef)]/@id';
return $this->filterXML($xml, $query);
}
/**
* Performs an XPath query to get the targetRef and SourceRef Node Id
*/
private function getRefNodes(SimpleXMLElement $xml, string $sequenceFlowId): Collection
{
$sequenceFlowNode = $xml->xpath("//bpmn:sequenceFlow[@id='{$sequenceFlowId}']");
if (empty($sequenceFlowNode)) {
return collect();
}
return collect([
'targetRef' => (string) $sequenceFlowNode[0]['targetRef'],
'sourceRef' => (string) $sequenceFlowNode[0]['sourceRef'],
]);
}
/**
* Validates if the sourceRef token is in progress when the repeat count is the same as the targetRef
*/
private function getCountFlag(int $sourceCount, int $targetCount, string $sourceRef, ProcessRequest $request) :bool
{
$maxToken = $request->tokens()->find($this->getMaxTokenId($request, $sourceRef));
return $maxToken->status === 'ACTIVE' && $sourceCount === $targetCount;
}
private function loadProcessMap(ProcessRequest $request): array
{
$processRequest = ProcessRequest::find($request->id);
// Get the bpmn related to the version
$bpmn = $request->process->versions()
->where('id', $request->process_version_id)
->select('bpmn')
->firstOrFail()
->bpmn;
$filteredCompletedNodes = [];
$requestInProgressNodes = [];
$requestIdleNodes = [];
if ($processRequest) {
$requestCompletedNodes = $processRequest->tokens()
->whereIn('status', ['CLOSED', 'COMPLETED', 'TRIGGERED'])
->pluck('element_id');
$requestInProgressNodes = $processRequest->tokens()
->whereIn('status', ['ACTIVE', 'INCOMING'])
->pluck('element_id');
// Remove any node that is 'ACTIVE' from the completed list.
$filteredCompletedNodes = $requestCompletedNodes->diff($requestInProgressNodes)->values();
// Obtain In-Progress nodes that were completed before
$matchingNodes = $requestInProgressNodes->intersect($requestCompletedNodes);
// Get idle nodes.
$xml = $this->loadAndPrepareXML($bpmn);
$nodeIds = $this->getNodeIds($xml);
$requestIdleNodes = $nodeIds->diff($filteredCompletedNodes)->diff($requestInProgressNodes)->values();
// Add completed sequence flow to the list of completed nodes.
$sequenceFlowNodes = $this->getCompletedSequenceFlow($xml, $filteredCompletedNodes->implode(' '), $requestInProgressNodes->implode(' '), $matchingNodes->implode(' '));
$filteredCompletedNodes = $filteredCompletedNodes->merge($sequenceFlowNodes);
}
return [
'bpmn' => $bpmn,
'requestCompletedNodes' => $filteredCompletedNodes,
'requestInProgressNodes' => $requestInProgressNodes,
'requestIdleNodes' => $requestIdleNodes,
'requestId' => $request->id,
];
}
/**
* Get the stages for a specific process ID.
*
* @param int $processId
* @return array|null
*/
public static function getStagesByProcessId(int $processId): ?array
{
// Retrieve the process by ID
$process = ModelsProcess::find($processId);
// If the process exists, return the stages
if ($process) {
$stages = $process->stages;
if (!is_null($stages)) {
return $stages;
}
}
// Return empty array if the process does not exist or has no stages
return [];
}
}