diff --git a/ProcessMaker/Http/Controllers/Api/ProcessController.php b/ProcessMaker/Http/Controllers/Api/ProcessController.php
index 0c223f70da..6ebd6a4916 100644
--- a/ProcessMaker/Http/Controllers/Api/ProcessController.php
+++ b/ProcessMaker/Http/Controllers/Api/ProcessController.php
@@ -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;
@@ -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']);
diff --git a/ProcessMaker/Nayra/Services/FixBpmnSchemaService.php b/ProcessMaker/Nayra/Services/FixBpmnSchemaService.php
new file mode 100644
index 0000000000..4c3cf3ffff
--- /dev/null
+++ b/ProcessMaker/Nayra/Services/FixBpmnSchemaService.php
@@ -0,0 +1,138 @@
+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;
+ }
+ }
+}
diff --git a/tests/Fixtures/process_data_input_generated_in_pm4.bpmn b/tests/Fixtures/process_data_input_generated_in_pm4.bpmn
new file mode 100644
index 0000000000..4a4d0839c8
--- /dev/null
+++ b/tests/Fixtures/process_data_input_generated_in_pm4.bpmn
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+ data_input_node_16
+
+
+
+
+ node_16
+ data_input_node_16
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Fixtures/process_data_input_without_targetref.bpmn b/tests/Fixtures/process_data_input_without_targetref.bpmn
new file mode 100644
index 0000000000..e6c346b2bf
--- /dev/null
+++ b/tests/Fixtures/process_data_input_without_targetref.bpmn
@@ -0,0 +1,110 @@
+
+
+
+
+
+ _1446983_5
+ _1446983_8
+ _1446983_20
+ _1446983_29
+
+
+ _1446983_12
+ _1446983_14
+
+
+
+
+
+
+
+
+
+ dor_1446983_36
+
+
+
+
+
+
+
+ dor_1446983_38
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/unit/ProcessMaker/Nayra/Services/FixBpmnSchemaServiceTest.php b/tests/unit/ProcessMaker/Nayra/Services/FixBpmnSchemaServiceTest.php
new file mode 100644
index 0000000000..2b3ec5adaf
--- /dev/null
+++ b/tests/unit/ProcessMaker/Nayra/Services/FixBpmnSchemaServiceTest.php
@@ -0,0 +1,84 @@
+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);
+ }
+}