Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/sonarqube.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

- name: Cache Composer dependencies
id: composer-cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: vendor
key: composer-v1-${{ hashFiles('composer.json') }}
Expand Down
46 changes: 46 additions & 0 deletions src/ProcessMaker/Nayra/Storage/BpmnDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,52 @@ public function __construct($version = null, $encoding = null)
$this->registerNodeClass(DOMElement::class, BpmnElement::class);
}

/**
* Load the XML source and replace the html entities with the correct characters.
*
* This is a workaround to avoid the DOMDocument::loadXML() function to throw an error when
* the XML contains html entities.
* HTML entities can be introduced by the ProcessMaker Modeler when the user inserts html
* tags in the documentation field of a task.
*
* @param string $source
* @param int $options
* @return bool
*/
public function loadXML(string $source, int $options = 0): bool
{
$source = self::replaceHtmlEntities($source);
return parent::loadXML($source, $options);
}

/**
* Replace the html entities with the correct characters.
*
* @param string $source
*
* @return string
*/
public static function replaceHtmlEntities($source)
{
$source = str_replace([
' ',
'<',
'>',
'&',
''',
'"',
], [
' ',
'<',
'>',
'&',
''',
'"',
], $source);

return $source;
}

/**
* Set the factory used to create BPMN elements.
*
Expand Down
118 changes: 118 additions & 0 deletions tests/Feature/Engine/BpmnDocumentHtmlEntitiesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace Tests\Feature\Engine;

use ProcessMaker\Nayra\Storage\BpmnDocument;
use DOMDocument;

/**
* Test for handling HTML entities in BPMN files.
*/
class BpmnDocumentHtmlEntitiesTest extends EngineTestCase
{
/**
* Test the replaceHtmlEntities method directly.
*/
public function testReplaceHtmlEntities()
{
// String with HTML entities
$source = 'This is a text with &lt;html&gt; entities &amp; special chars like &quot;quotes&quot; and &apos;apostrophes&apos; and &nbsp;spaces.';

// Expected result after replacement
$expected = 'This is a text with &#60;html&#62; entities &#38; special chars like &#34;quotes&#34; and &#39;apostrophes&#39; and &#160;spaces.';

// Test the static method
$result = BpmnDocument::replaceHtmlEntities($source);

// Assert the result matches the expected output
$this->assertEquals($expected, $result);
}

/**
* Test loadXML with HTML entities.
*/
public function testLoadXmlWithHtmlEntities()
{
// Create a BPMN XML string with HTML entities in the documentation tag
$bpmnXml = file_get_contents(__DIR__ . '/files/BpmnWithHtmlEntities.bpmn');

// 1. First try to load with regular DOMDocument - should fail or produce incorrect results
$regularDom = new DOMDocument();
$regularLoaded = @$regularDom->loadXML($bpmnXml); // @ to suppress warnings

// If it loads without errors, check that the content is different from what we expect
if ($regularLoaded) {
$startEventDoc = $regularDom->getElementsByTagName('documentation')->item(0);
$originalContent = $startEventDoc ? $startEventDoc->textContent : '';

// The content should be mangled or different from what we expect with proper entity handling
$expectedContent = 'This contains <b>HTML</b> entities & special chars like "quotes" and \'apostrophes\' and spaces.';
$this->assertNotEquals($expectedContent, $originalContent, 'Standard DOMDocument should not correctly handle HTML entities');
}

// 2. Now load with BpmnDocument which should handle HTML entities correctly
$bpmnDocument = new BpmnDocument();
$bpmnDocument->setEngine($this->engine);
$bpmnDocument->setFactory($this->repository);

// Load the XML with HTML entities
$result = $bpmnDocument->loadXML($bpmnXml);
$this->assertTrue($result, 'BpmnDocument should successfully load the XML with HTML entities');

// Verify that documentation tags contain correctly converted entities
$startEventDoc = $bpmnDocument->getElementsByTagName('documentation')->item(0);
$this->assertNotNull($startEventDoc, 'Documentation element should exist');

// The text content should have the HTML entities properly converted
$nbsp = "\xC2\xA0";
$expectedContent = 'This contains <b>HTML</b> entities & special chars like "quotes" and \'apostrophes\' and ' . $nbsp . 'spaces.';
$this->assertEquals($expectedContent, $startEventDoc->textContent, 'HTML entities should be correctly converted');

// Check the second documentation tag too
$taskDoc = $bpmnDocument->getElementsByTagName('documentation')->item(1);
$this->assertNotNull($taskDoc, 'Second documentation element should exist');
$expectedTaskContent = 'Another <strong>documentation</strong> with & entities.';
$this->assertEquals($expectedTaskContent, $taskDoc->textContent, 'HTML entities in second documentation should be correctly converted');
}

/**
* Test loading a complex BPMN with HTML entities in various places.
*/
public function testLoadComplexBpmnWithHtmlEntities()
{
// Create a more complex BPMN with HTML entities in various attributes and text content
$complexBpmnXml = file_get_contents(__DIR__ . '/files/BpmnWithComplexHtml.bpmn');

// Load with BpmnDocument
$bpmnDocument = new BpmnDocument();
$bpmnDocument->setEngine($this->engine);
$bpmnDocument->setFactory($this->repository);

$result = $bpmnDocument->loadXML($complexBpmnXml);
$this->assertTrue($result, 'BpmnDocument should successfully load complex XML with HTML entities');

// Check process name attribute
$process = $bpmnDocument->getElementsByTagName('process')->item(0);
$this->assertEquals('Process & HTML entities', $process->getAttribute('name'), 'Process name attribute should have entities converted');

// Check start event name attribute
$startEvent = $bpmnDocument->getElementsByTagName('startEvent')->item(0);
$this->assertEquals('Start <event>', $startEvent->getAttribute('name'), 'Start event name attribute should have entities converted');

// Check documentation content
$documentation = $startEvent->getElementsByTagName('documentation')->item(0);
$this->assertEquals(
'Documentation with <ul><li>HTML list</li></ul> and & "quotes"',
$documentation->textContent,
'Documentation should have entities converted'
);

// Check sequence flow name attribute
$sequenceFlow = $bpmnDocument->getElementsByTagName('sequenceFlow')->item(0);
$this->assertEquals(
'Flow with & special "chars"',
$sequenceFlow->getAttribute('name'),
'Sequence flow name attribute should have entities converted'
);
}
}
24 changes: 24 additions & 0 deletions tests/Feature/Engine/files/BpmnWithComplexHtml.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions 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"
id="Definitions_1"
name="Process with &lt;special&gt; &amp; chars"
targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="Process_1" name="Process &amp; HTML entities" isExecutable="true">
<bpmn:startEvent id="StartEvent_1" name="Start &lt;event&gt;">
<bpmn:documentation>Documentation with &lt;ul&gt;&lt;li&gt;HTML list&lt;/li&gt;&lt;/ul&gt; and &amp; &quot;quotes&quot;</bpmn:documentation>
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_1" name="Flow with &amp; special &quot;chars&quot;" sourceRef="StartEvent_1" targetRef="Task_1" />
<bpmn:task id="Task_1" name="Task &amp; more">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_2</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="Task_1" targetRef="EndEvent_1" />
<bpmn:endEvent id="EndEvent_1" name="End &lt;event&gt;">
<bpmn:incoming>SequenceFlow_2</bpmn:incoming>
</bpmn:endEvent>
</bpmn:process>
</bpmn:definitions>
24 changes: 24 additions & 0 deletions tests/Feature/Engine/files/BpmnWithHtmlEntities.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions 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"
id="Definitions_1"
targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:documentation>This contains &lt;b&gt;HTML&lt;/b&gt; entities &amp; special chars like &quot;quotes&quot; and &apos;apostrophes&apos; and &nbsp;spaces.</bpmn:documentation>
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:task id="Task_1">
<bpmn:documentation>Another &lt;strong&gt;documentation&lt;/strong&gt; with &amp; entities.</bpmn:documentation>
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_2</bpmn:outgoing>
</bpmn:task>
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>SequenceFlow_2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="StartEvent_1" targetRef="Task_1" />
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="Task_1" targetRef="EndEvent_1" />
</bpmn:process>
</bpmn:definitions>