Skip to content
7 changes: 7 additions & 0 deletions ProcessMaker/Http/Controllers/Api/ProcessController.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use ProcessMaker\Models\Script;
use ProcessMaker\Models\Template;
use ProcessMaker\Nayra\Exceptions\ElementNotFoundException;
use ProcessMaker\Nayra\Services\FixBpmnSchemaService;
use ProcessMaker\Nayra\Storage\BpmnDocument;
use ProcessMaker\Package\Translations\Models\Language;
use ProcessMaker\Package\WebEntry\Models\WebentryRoute;
Expand Down Expand Up @@ -379,6 +380,12 @@ public function store(Request $request)
// Validate if exists file bpmn
if ($request->has('file')) {
$data['bpmn'] = $request->file('file')->get();

// Some BPMN definitions created in others modelers doesn't comply BPMN 2.0
// So, should be fixed
$fixBpmnSchemaService = app(FixBpmnSchemaService::class);
$data['bpmn'] = $fixBpmnSchemaService->fix($data['bpmn']);

$request->merge(['bpmn' => $data['bpmn']]);
$request->offsetUnset('file');
unset($data['file']);
Expand Down
138 changes: 138 additions & 0 deletions ProcessMaker/Nayra/Services/FixBpmnSchemaService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

namespace ProcessMaker\Nayra\Services;

use DOMDocument;
use DOMXPath;
use Exception;

class FixBpmnSchemaService
{
/**
* Fix BPMN definition if is necesasary
*
* @param string $bpmn
* @return string
* @throws \Exception
*/
public static function fix(string $bpmn): string
{
try {
// Create main object
$document = new DOMDocument();
$document->preserveWhiteSpace = false;
$document->loadXml($bpmn);

// Get root node
$root = $document->documentElement;

// Set "targetNamespace" attribute always
$root->setAttribute('targetNamespace', 'http://www.omg.org/spec/BPMN/20100524/MODEL');

// Create XPath object
$xpath = new DOMXPath($document);

// Register all namespaces, including default
foreach ($document->documentElement->attributes as $attr) {
if (strpos($attr->nodeName, 'xmlns:') === 0) {
$prefix = substr($attr->nodeName, 6);
$xpath->registerNamespace($prefix, $attr->nodeValue);
} elseif ($attr->nodeName === 'xmlns') {
// Register default namespace as "def"
$xpath->registerNamespace('def', $attr->nodeValue);
}
}

// Find all task nodes
$taskNodes = $xpath->query('//*[local-name()="task"]');

foreach ($taskNodes as $task) {
$taskId = $task->getAttribute("id");
$taskPrefix = !empty($task->prefix) ? "$task->prefix:" : '';
$taskNS = $task->namespaceURI;

$dataInputAssociation = $xpath->query('./*[local-name()="dataInputAssociation"]', $task)->item(0);
if (!$dataInputAssociation) {
continue;
}

// Skip if targetRef already exists
$targetRefExists = $xpath->query('./*[local-name()="targetRef"]', $dataInputAssociation)->length > 0;
if ($targetRefExists) {
continue;
}

// Extract sourceRef
$sourceRef = $xpath->query('./*[local-name()="sourceRef"]', $dataInputAssociation)->item(0);
if (!$sourceRef) {
throw new Exception("sourceRef not found in dataInputAssociation for task $taskId");
}
$sourceId = $sourceRef->nodeValue;

// Create ioSpecification and children
$ioSpec = $document->createElementNS($taskNS, "{$taskPrefix}ioSpecification");
$ioSpecId = "{$taskId}_inner_" . round(microtime(true) * 1000);
$ioSpec->setAttribute("id", $ioSpecId);

$dataInputId = "data_input_{$sourceId}";
$dataInput = $document->createElementNS($taskNS, "{$taskPrefix}dataInput");
$dataInput->setAttribute("id", $dataInputId);
$dataInput->setAttribute("name", "Template for protocol");
$ioSpec->appendChild($dataInput);

$inputSet = $document->createElementNS($taskNS, "{$taskPrefix}inputSet");
$inputSet->setAttribute("id", "{$taskId}_inner_" . (round(microtime(true) * 1000) + 2));
$dataInputRefs = $document->createElementNS($taskNS, "{$taskPrefix}dataInputRefs", $dataInputId);
$inputSet->appendChild($dataInputRefs);
$ioSpec->appendChild($inputSet);

$outputSet = $document->createElementNS($taskNS, "{$taskPrefix}outputSet");
$outputSet->setAttribute("id", "{$taskId}_inner_" . (round(microtime(true) * 1000) + 3));
$ioSpec->appendChild($outputSet);

$task->insertBefore($ioSpec, $dataInputAssociation);

// Add targetRef
$targetRef = $document->createElementNS($taskNS, "{$taskPrefix}targetRef", $dataInputId);
$dataInputAssociation->appendChild($targetRef);

// Add BPMNEdge to BPMNDiagram
$diagramNodes = $xpath->query('//*[local-name() = "BPMNDiagram"]');
if ($diagramNodes->length === 0) {
throw new Exception("No BPMNDiagram node found in the BPMN file.");
}
$bpmnDiagram = $diagramNodes->item(0);
$diagramPrefix = $bpmnDiagram->prefix;
$diagramNS = $bpmnDiagram->namespaceURI;

$bpmnPlaneNodes = $xpath->query('.//*[local-name() = "BPMNPlane"]', $bpmnDiagram);
if ($bpmnPlaneNodes->length === 0) {
throw new Exception("No BPMNPlane found inside BPMNDiagram.");
}
$bpmnPlane = $bpmnPlaneNodes->item(0);

$edgeId = 'BPMNEdge_' . $dataInputAssociation->getAttribute("id");
$bpmnEdge = $document->createElementNS($diagramNS, "{$diagramPrefix}:BPMNEdge");
$bpmnEdge->setAttribute("id", $edgeId);
$bpmnEdge->setAttribute("bpmnElement", $dataInputAssociation->getAttribute("id"));

$diNS = 'http://www.omg.org/spec/DD/20100524/DI';
$waypoint1 = $document->createElementNS($diNS, "di:waypoint");
$waypoint1->setAttribute("x", "100");
$waypoint1->setAttribute("y", "100");

$waypoint2 = $document->createElementNS($diNS, "di:waypoint");
$waypoint2->setAttribute("x", "200");
$waypoint2->setAttribute("y", "200");

$bpmnEdge->appendChild($waypoint1);
$bpmnEdge->appendChild($waypoint2);
$bpmnPlane->appendChild($bpmnEdge);
}

return $document->saveXml();
} catch (Exception $e) {
throw $e;
}
}
}
33 changes: 33 additions & 0 deletions tests/Fixtures/process_data_input_generated_in_pm4.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:pm="http://processmaker.com/BPMN/2.0/Schema.xsd" xmlns:tns="http://sourceforge.net/bpmn/definitions/_1530553328908" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://bpmn.io/schema/bpmn" exporter="ProcessMaker Modeler" exporterVersion="1.0" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://bpmn.sourceforge.net/schemas/BPMN20.xsd">
<bpmn:process id="ProcessId" name="ProcessName" isExecutable="true">
<bpmn:task id="node_1" name="Form Task" pm:allowInterstitial="false" pm:assignment="requester" pm:assignmentLock="false" pm:allowReassignment="false" pm:elementDestination="{&#34;type&#34;:&#34;taskSource&#34;,&#34;value&#34;:null}">
<bpmn:ioSpecification id="node_1_inner_1753895979606">
<bpmn:dataInput id="data_input_node_16" name="Data Object" />
<bpmn:inputSet id="node_1_inner_1753895979608">
<bpmn:dataInputRefs>data_input_node_16</bpmn:dataInputRefs>
</bpmn:inputSet>
<bpmn:outputSet id="node_1_inner_1753895979609" />
</bpmn:ioSpecification>
<bpmn:dataInputAssociation id="node_17">
<bpmn:sourceRef>node_16</bpmn:sourceRef>
<bpmn:targetRef>data_input_node_16</bpmn:targetRef>
</bpmn:dataInputAssociation>
</bpmn:task>
<bpmn:dataObjectReference id="node_16" name="Data Object" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagramId">
<bpmndi:BPMNPlane id="BPMNPlaneId" bpmnElement="ProcessId">
<bpmndi:BPMNShape id="node_1_di" bpmnElement="node_1">
<dc:Bounds x="320" y="200" width="116" height="76" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="node_16_di" bpmnElement="node_16">
<dc:Bounds x="350" y="400" width="36" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="node_17_di" bpmnElement="node_17">
<di:waypoint x="368" y="425" />
<di:waypoint x="378" y="238" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
110 changes: 110 additions & 0 deletions tests/Fixtures/process_data_input_without_targetref.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns4:definitions xmlns="http://www.omg.org/spec/DD/20100524/DC" xmlns:ns2="http://www.omg.org/spec/DD/20100524/DI" xmlns:ns3="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:ns4="http://www.omg.org/spec/BPMN/20100524/MODEL" exporter="CWA SmartProcess" exporterVersion="25.3">
<ns4:process name="Test Process" id="_1446983">
<ns4:laneSet id="tls_1446983">
<ns4:lane name="Lane 1" id="_1446983_4">
<ns4:flowNodeRef>_1446983_5</ns4:flowNodeRef>
<ns4:flowNodeRef>_1446983_8</ns4:flowNodeRef>
<ns4:flowNodeRef>_1446983_20</ns4:flowNodeRef>
<ns4:flowNodeRef>_1446983_29</ns4:flowNodeRef>
</ns4:lane>
<ns4:lane name="Lane 2" id="_1446983_6">
<ns4:flowNodeRef>_1446983_12</ns4:flowNodeRef>
<ns4:flowNodeRef>_1446983_14</ns4:flowNodeRef>
</ns4:lane>
</ns4:laneSet>
<ns4:dataObject name="Data Input" id="_1446983_36"/>
<ns4:dataObjectReference dataObjectRef="_1446983_36" name="Data Input" id="dor_1446983_36"/>
<ns4:dataObject name="Data Output" id="_1446983_38"/>
<ns4:dataObjectReference dataObjectRef="_1446983_38" name="Data Output" id="dor_1446983_38"/>
<ns4:startEvent name="Start Process" id="_1446983_5"/>
<ns4:task name="Task 1" id="_1446983_8">
<ns4:dataInputAssociation id="_1446983_40">
<ns4:sourceRef>dor_1446983_36</ns4:sourceRef>
</ns4:dataInputAssociation>
</ns4:task>
<ns4:task name="Task 4" id="_1446983_20"/>
<ns4:endEvent name="End Process" id="_1446983_29"/>
<ns4:task name="Task 2" id="_1446983_12"/>
<ns4:task name="Task 3" id="_1446983_14">
<ns4:dataOutputAssociation id="_1446983_39">
<ns4:targetRef>dor_1446983_38</ns4:targetRef>
</ns4:dataOutputAssociation>
</ns4:task>
<ns4:sequenceFlow sourceRef="_1446983_8" targetRef="_1446983_12" id="_1446983_13"/>
<ns4:sequenceFlow sourceRef="_1446983_14" targetRef="_1446983_20" id="_1446983_21"/>
<ns4:sequenceFlow sourceRef="_1446983_5" targetRef="_1446983_8" id="_1446983_9"/>
<ns4:sequenceFlow sourceRef="_1446983_12" targetRef="_1446983_14" id="_1446983_15"/>
</ns4:process>
<ns3:BPMNDiagram name="Test Process" resolution="10.0">
<ns3:BPMNPlane xmlns:ns5="http://www.omg.org/spec/DD/20100524/DC" xmlns="" bpmnElement="_1446983" id="bp_1446983">
<ns3:BPMNShape bpmnElement="_1446983_4" isHorizontal="true">
<ns5:Bounds x="10.0" y="60.0" width="1150.0" height="210.0"/>
<ns3:BPMNLabel>
<ns5:Bounds x="10.0" y="60.0" width="23.0" height="210.0"/>
</ns3:BPMNLabel>
</ns3:BPMNShape>
<ns3:BPMNShape bpmnElement="_1446983_6" isHorizontal="true">
<ns5:Bounds x="10.0" y="270.0" width="1150.0" height="210.0"/>
<ns3:BPMNLabel>
<ns5:Bounds x="10.0" y="270.0" width="23.0" height="210.0"/>
</ns3:BPMNLabel>
</ns3:BPMNShape>
<ns3:BPMNShape bpmnElement="dor_1446983_36">
<ns5:Bounds x="225.0" y="490.0" width="30.0" height="40.0"/>
</ns3:BPMNShape>
<ns3:BPMNShape bpmnElement="dor_1446983_38">
<ns5:Bounds x="485.0" y="500.0" width="30.0" height="40.0"/>
</ns3:BPMNShape>
<ns3:BPMNShape bpmnElement="_1446983_5">
<ns5:Bounds x="80.0" y="145.0" width="40.0" height="40.0"/>
</ns3:BPMNShape>
<ns3:BPMNShape bpmnElement="_1446983_8">
<ns5:Bounds x="190.0" y="135.0" width="100.0" height="60.0"/>
</ns3:BPMNShape>
<ns3:BPMNShape bpmnElement="_1446983_20">
<ns5:Bounds x="560.0" y="145.0" width="100.0" height="60.0"/>
</ns3:BPMNShape>
<ns3:BPMNShape bpmnElement="_1446983_29">
<ns5:Bounds x="880.0" y="155.0" width="40.0" height="40.0"/>
</ns3:BPMNShape>
<ns3:BPMNShape bpmnElement="_1446983_12">
<ns5:Bounds x="300.0" y="340.0" width="100.0" height="60.0"/>
</ns3:BPMNShape>
<ns3:BPMNShape bpmnElement="_1446983_14">
<ns5:Bounds x="450.0" y="340.0" width="100.0" height="60.0"/>
</ns3:BPMNShape>
<ns3:BPMNEdge bpmnElement="_1446983_13">
<ns2:waypoint x="240.0" y="195.0"/>
<ns2:waypoint x="240.0" y="370.0"/>
<ns2:waypoint x="300.0" y="370.0"/>
</ns3:BPMNEdge>
<ns3:BPMNEdge bpmnElement="_1446983_21">
<ns2:waypoint x="550.0" y="370.0"/>
<ns2:waypoint x="610.0" y="370.0"/>
<ns2:waypoint x="610.0" y="272.5"/>
<ns2:waypoint x="610.0" y="205.0"/>
</ns3:BPMNEdge>
<ns3:BPMNEdge bpmnElement="_1446983_39">
<ns2:waypoint x="500.0" y="400.0"/>
<ns2:waypoint x="500.0" y="450.0"/>
<ns2:waypoint x="500.0" y="500.0"/>
</ns3:BPMNEdge>
<ns3:BPMNEdge bpmnElement="_1446983_40">
<ns2:waypoint x="240.0" y="490.0"/>
<ns2:waypoint x="240.0" y="342.5"/>
<ns2:waypoint x="240.0" y="195.0"/>
</ns3:BPMNEdge>
<ns3:BPMNEdge bpmnElement="_1446983_9">
<ns2:waypoint x="120.0" y="165.0"/>
<ns2:waypoint x="155.0" y="165.0"/>
<ns2:waypoint x="190.0" y="165.0"/>
</ns3:BPMNEdge>
<ns3:BPMNEdge bpmnElement="_1446983_15">
<ns2:waypoint x="400.0" y="370.0"/>
<ns2:waypoint x="425.0" y="370.0"/>
<ns2:waypoint x="450.0" y="370.0"/>
</ns3:BPMNEdge>
</ns3:BPMNPlane>
</ns3:BPMNDiagram>
</ns4:definitions>
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace Tests\Unit\ProcessMaker\Nayra\Services;

use Exception;
use ProcessMaker\Nayra\Services\FixBpmnSchemaService;
use ProcessMaker\Nayra\Storage\BpmnDocument;
use Tests\TestCase;

/**
* Class FixBpmnSchemaServiceTest
*/
class FixBpmnSchemaServiceTest extends TestCase
{
/**
* Test with BPMN definition created in another modeler,
* an Exception should be throwed
*
* @return void
*/
public function testExceptionInIncompleteProcess()
{
$bpmn = file_get_contents(
__DIR__ .
"/../../../../Fixtures/process_data_input_without_targetref.bpmn"
);

$document = new BpmnDocument();
$document->loadXML($bpmn);

$this->expectException(Exception::class);
$validation = $document->validateBPMNSchema(
public_path("definitions/ProcessMaker.xsd")
);
}

/**
* Test fixing incomplete BPMN definition created in another modeler
*
* @return void
*/
public function testFixIncompleteProcess()
{
$bpmn = file_get_contents(
__DIR__ .
"/../../../../Fixtures/process_data_input_without_targetref.bpmn"
);

$fixBpmnSchemaService = app(FixBpmnSchemaService::class);
$bpmn = $fixBpmnSchemaService->fix($bpmn);

$document = new BpmnDocument();
$document->loadXML($bpmn);
$validation = $document->validateBPMNSchema(
public_path("definitions/ProcessMaker.xsd")
);

$this->assertTrue($validation);
}

/**
* Test using a BPMN definition created in PM4
*
* @return void
*/
public function testFixPm4Process()
{
$bpmn = file_get_contents(
__DIR__ .
"/../../../../Fixtures/process_data_input_generated_in_pm4.bpmn"
);

$fixBpmnSchemaService = app(FixBpmnSchemaService::class);
$bpmn = $fixBpmnSchemaService->fix($bpmn);

$document = new BpmnDocument();
$document->loadXML($bpmn);
$validation = $document->validateBPMNSchema(
public_path("definitions/ProcessMaker.xsd")
);

$this->assertTrue($validation);
}
}