getDiagramBoundsFromBpmnDi()   F
last analyzed

Complexity

Conditions 23
Paths 306

Size

Total Lines 62
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 62
rs 2.0083
c 0
b 0
f 0
cc 23
nc 306
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Jabe\Impl\Bpmn\Diagram;
4
5
use PHPixie\Image as PImage;
6
use PHPixie\Image\Drivers\Driver\Resource as ImageResource;
7
use Jabe\{
8
    ProcessEngineException,
9
    RepositoryServiceInterface
10
};
11
use Jabe\Impl\Bpmn\Parser\BpmnParser;
12
use Jabe\Impl\Context\Context;
13
use Jabe\Repository\{
14
    DiagramElement,
15
    DiagramLayout,
16
    DiagramNode
17
};
18
use Xml\Impl\Instance\{
19
    DomDocumentExt,
20
    DomElementExt
21
};
22
23
class ProcessDiagramLayoutFactory
24
{
25
    private const GREY_THRESHOLD = 175;
26
27
    // Parser features and their values needed to disable XXE Parsing
28
    private const XXE_FEATURES = [
29
        "http://apache.org/xml/features/disallow-doctype-decl" => true,
30
        "http://xml.org/sax/features/external-general-entities" => false,
31
        "http://xml.org/sax/features/external-parameter-entities" => false,
32
        "http://apache.org/xml/features/nonvalidating/load-external-dtd" => false
33
    ];
34
35
    /**
36
     * Provides positions and dimensions of elements in a process diagram as
37
     * provided by RepositoryService#getProcessDiagram(String).
38
     *
39
     * Currently, it only supports BPMN 2.0 models.
40
     *
41
     * @param bpmnXmlStream
42
     *          BPMN 2.0 XML file
43
     * @param imageStream
44
     *          BPMN 2.0 diagram in PNG format (JPEG and other formats supported
45
     *          by ImageIO may also work)
46
     * @return Layout of the process diagram
47
     * @return DiagramLayout null when parameter imageStream is null
48
     */
49
    public function getProcessDiagramLayout($bpmnXmlStream, $imageStream = null): ?DiagramLayout
50
    {
51
        $bpmnModel = $this->parseXml($bpmnXmlStream);
52
        return $this->getBpmnProcessDiagramLayout($bpmnModel, $imageStream);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getBpmnPro...pmnModel, $imageStream) also could return the type Jabe\Repository\DiagramLayout which is incompatible with the documented return type Jabe\Impl\Bpmn\Diagram\Layout.
Loading history...
53
    }
54
55
    /**
56
     * Provides positions and dimensions of elements in a BPMN process diagram as
57
     * provided by RepositoryService#getProcessDiagram(String).
58
     *
59
     * @param bpmnModel
60
     *          BPMN 2.0 XML document
61
     * @param imageStream
62
     *          BPMN 2.0 diagram in PNG format (JPEG and other formats supported
63
     *          by ImageIO may also work)
64
     * @return Layout of the process diagram
65
     * @return DiagramLayout null when parameter imageStream is null
66
     */
67
    public function getBpmnProcessDiagramLayout(DomDocumentExt $bpmnModel, $imageStream = null): ?DiagramLayout
68
    {
69
        if ($imageStream === null) {
70
            return null;
71
        }
72
        $diagramBoundsXml = $this->getDiagramBoundsFromBpmnDi($bpmnModel);
73
        $diagramBoundsImage = $this->getDiagramBoundsFromImage($imageStream);
74
75
        $listOfBounds = [];
76
        $listOfBounds[$diagramBoundsXml->getId()] = $diagramBoundsXml;
77
        $listOfBounds = array_merge($listOfBounds, $this->getElementBoundsFromBpmnDi($bpmnModel));
78
79
        $listOfBoundsForImage = $this->transformBoundsForImage($diagramBoundsImage, $diagramBoundsXml, $listOfBounds);
0 ignored issues
show
Bug introduced by
It seems like $diagramBoundsImage can also be of type null; however, parameter $diagramBoundsImage of Jabe\Impl\Bpmn\Diagram\P...ansformBoundsForImage() does only seem to accept Jabe\Repository\DiagramNode, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

79
        $listOfBoundsForImage = $this->transformBoundsForImage(/** @scrutinizer ignore-type */ $diagramBoundsImage, $diagramBoundsXml, $listOfBounds);
Loading history...
80
        return new DiagramLayout($listOfBoundsForImage);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Jabe\Reposito...($listOfBoundsForImage) returns the type Jabe\Repository\DiagramLayout which is incompatible with the documented return type Jabe\Impl\Bpmn\Diagram\Layout.
Loading history...
Bug introduced by
It seems like $listOfBoundsForImage can also be of type null; however, parameter $elements of Jabe\Repository\DiagramLayout::__construct() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

80
        return new DiagramLayout(/** @scrutinizer ignore-type */ $listOfBoundsForImage);
Loading history...
81
    }
82
83
    protected function parseXml($bpmnXmlStream): DomDocumentExt
84
    {
85
        $bpmnModel = new DomDocumentExt();
86
        try {
87
            $meta = stream_get_meta_data($bpmnXmlStream);
88
            $bpmnModel->loadXML(fread($bpmnXmlStream, filesize($meta['uri'])));
89
        } catch (\Exception $e) {
90
            throw new ProcessEngineException("Error while parsing BPMN model.", $e);
91
        }
92
        return $bpmnModel;
93
    }
94
95
    protected function getDiagramBoundsFromBpmnDi(DomDocumentExt $bpmnModel): DiagramNode
96
    {
97
        $minX = null;
98
        $minY = null;
99
        $maxX = null;
100
        $maxY = null;
101
102
        // Node positions and dimensions
103
        $setOfBounds = $bpmnModel->getElementsByTagNameNS(BpmnParser::BPMN_DC_NS, "Bounds");
104
        for ($i = 0; $i < $setOfBounds->count(); $i += 1) {
105
            $element = $setOfBounds->item($i);
106
            $x = floatval($element->getAttribute("x"));
107
            $y = floatval($element->getAttribute("y"));
108
            $width = floatval($element->getAttribute("width"));
109
            $height = floatval($element->getAttribute("height"));
110
111
            if ($x == 0 && $y == 0 && $width == 0 && $height == 0) {
112
                // Ignore empty labels like the ones produced by Yaoqiang:
113
                // <bpmndi:BPMNLabel><dc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/></bpmndi:BPMNLabel>
114
            } else {
115
                if ($minX === null || $x < $minX) {
116
                    $minX = $x;
117
                }
118
                if ($minY === null || $y < $minY) {
119
                    $minY = $y;
120
                }
121
                if ($maxX === null || $maxX < ($x + $width)) {
122
                    $maxX = ($x + $width);
123
                }
124
                if ($maxY === null || $maxY < ($y + $height)) {
125
                    $maxY = ($y + $height);
126
                }
127
            }
128
        }
129
130
        // Edge bend points
131
        $waypoints = $bpmnModel->getElementsByTagNameNS(BpmnParser::OMG_DI_NS, "waypoint");
132
        for ($i = 0; $i < $waypoints->count(); $i += 1) {
133
            $waypoint = $waypoints->item($i);
134
            $x = floatval($waypoint->getAttribute("x"));
135
            $y = floatval($waypoint->getAttribute("y"));
136
137
            if ($minX === null || $x < $minX) {
138
                $minX = $x;
139
            }
140
            if ($minY === null || $y < $minY) {
141
                $minY = $y;
142
            }
143
            if ($maxX === null || $maxX < $x) {
144
                $maxX = $x;
145
            }
146
            if ($maxY === null || $maxY < $y) {
147
                $maxY = $y;
148
            }
149
        }
150
151
        $diagramBounds = new DiagramNode("BPMNDiagram");
152
        $diagramBounds->setX($minX);
0 ignored issues
show
Bug introduced by
It seems like $minX can also be of type null; however, parameter $x of Jabe\Repository\DiagramNode::setX() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

152
        $diagramBounds->setX(/** @scrutinizer ignore-type */ $minX);
Loading history...
153
        $diagramBounds->setY($minY);
0 ignored issues
show
Bug introduced by
It seems like $minY can also be of type null; however, parameter $y of Jabe\Repository\DiagramNode::setY() does only seem to accept double, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

153
        $diagramBounds->setY(/** @scrutinizer ignore-type */ $minY);
Loading history...
154
        $diagramBounds->setWidth($maxX - $minX);
155
        $diagramBounds->setHeight($maxY - $minY);
156
        return $diagramBounds;
157
    }
158
159
    protected function getDiagramBoundsFromImage($resource, int $offsetTop = 0, int $offsetBottom = 0): ?DiagramNode
160
    {
161
        if (is_resource($resource)) {
162
            try {
163
                $meta = stream_get_meta_data($resource);
164
                $image = (new PImage())->read($meta['uri']);
165
            } catch (\Exception $e) {
166
                throw new ProcessEngineException("Error while reading process diagram image.", $e);
167
            }
168
            $diagramBoundsImage = $this->getDiagramBoundsFromImage($image, $offsetTop, $offsetBottom);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $diagramBoundsImage is correct as $this->getDiagramBoundsF...fsetTop, $offsetBottom) targeting Jabe\Impl\Bpmn\Diagram\P...iagramBoundsFromImage() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
169
            return $diagramBoundsImage;
170
        } elseif ($resource instanceof ImageResource) {
171
            $width = $resource->width();
172
            $height = $resource->height();
173
174
            $rowIsWhite = [];
175
            $columnIsWhite = [];
176
177
            for ($row = 0; $row < $height; $row += 1) {
178
                if (!array_key_exists($row, $rowIsWhite)) {
179
                    $rowIsWhite[$row] = true;
180
                }
181
                if ($row <= $offsetTop || $row >  $resource->height() - $offsetBottom) {
182
                    $rowIsWhite[$row] = true;
183
                } else {
184
                    for ($column = 0; $column < $width; $column += 1) {
185
                        if (!array_key_exists($column, $columnIsWhite)) {
186
                            $columnIsWhite[$column] = true;
187
                        }
188
                        $pixel = $resource->getPixel($column, $row);
189
                        $color = $pixel->color();
190
                        $alpha = $pixel->opacity();
191
                        $red   = ($color >> 16) & 0xFF;
192
                        $green = ($color >>  8) & 0xFF;
193
                        $blue  = ($color >>  0) & 0xFF;
194
                        if (!($alpha == 0 || ($red >= self::GREY_THRESHOLD && $green >= self::GREY_THRESHOLD && $blue >= self::GREY_THRESHOLD))) {
195
                            $rowIsWhite[$row] = false;
196
                            $columnIsWhite[$column] = false;
197
                        }
198
                    }
199
                }
200
            }
201
202
            $marginTop = 0;
203
            for ($row = 0; $row < $height; $row += 1) {
204
                if (array_key_exists($row, $rowIsWhite) && $rowIsWhite[$row]) {
205
                    $marginTop += 1;
206
                } else {
207
                    // Margin Top Found
208
                    break;
209
                }
210
            }
211
212
            $marginLeft = 0;
213
            for ($column = 0; $column < $width; $column++) {
214
                if (array_key_exists($column, $columnIsWhite) && $columnIsWhite[$column]) {
215
                    $marginLeft += 1;
216
                } else {
217
                    // Margin Left Found
218
                    break;
219
                }
220
            }
221
222
            $marginRight = 0;
223
            for ($column = $width - 1; $column >= 0; $column -= 1) {
224
                if (array_key_exists($column, $columnIsWhite) && $columnIsWhite[$column]) {
225
                    $marginRight += 1;
226
                } else {
227
                    // Margin Right Found
228
                    break;
229
                }
230
            }
231
232
            $marginBottom = 0;
233
            for ($row = $height - 1; $row >= 0; $row -= 1) {
234
                if (array_key_exists($row, $rowIsWhite) && $rowIsWhite[$row]) {
235
                    $marginBottom += 1;
236
                } else {
237
                    // Margin Bottom Found
238
                    break;
239
                }
240
            }
241
242
            $diagramBoundsImage = new DiagramNode();
243
            $diagramBoundsImage->setX($marginLeft);
244
            $diagramBoundsImage->setY($marginTop);
245
            $diagramBoundsImage->setWidth($width - $marginRight - $marginLeft);
246
            $diagramBoundsImage->setHeight($height - $marginBottom - $marginTop);
247
            return $diagramBoundsImage;
248
        }
249
    }
250
251
    protected function getElementBoundsFromBpmnDi(DomDocumentExt $bpmnModel): array
252
    {
253
        $listOfBounds = [];
254
        // iterate over all DI shapes
255
        $shapes = $bpmnModel->getElementsByTagNameNS(BpmnParser::BPMN_DI_NS, "BPMNShape");
256
        for ($i = 0; $i < $shapes->count(); $i += 1) {
257
            $shape = $shapes->item($i);
258
            $bpmnElementId = $shape->getAttribute("bpmnElement");
259
            // get bounds of shape
260
            $childNodes = $shape->childNodes;
261
            for ($j = 0; $j < $childNodes->count(); $j += 1) {
262
                $childNode = $childNodes->item($j);
263
                if (
264
                    $childNode instanceof DomElementExt &&
265
                    BpmnParser::BPMN_DC_NS == $childNode->namespaceURI &&
266
                    $childNode->localName == "Bounds"
267
                ) {
268
                    $bounds = $this->parseBounds($childNode);
269
                    $bounds->setId($bpmnElementId);
270
                    $listOfBounds[$bpmnElementId] = $bounds;
271
                    break;
272
                }
273
            }
274
        }
275
        return $listOfBounds;
276
    }
277
278
    protected function parseBounds(DomElementExt $boundsElement): DiagramNode
279
    {
280
        $bounds = new DiagramNode();
281
        $bounds->setX(floatval($boundsElement->getAttribute("x")));
282
        $bounds->setY(floatval($boundsElement->getAttribute("y")));
283
        $bounds->setWidth(floatval($boundsElement->getAttribute("width")));
284
        $bounds->setHeight(floatval($boundsElement->getAttribute("height")));
285
        return $bounds;
286
    }
287
288
    protected function transformBoundsForImage(DiagramNode $diagramBoundsImage, DiagramNode $diagramBoundsXml, $elementBounds)
289
    {
290
        if ($elementBounds instanceof DiagramNode) {
291
            $scalingFactorX = $diagramBoundsImage->getWidth() / $diagramBoundsXml->getWidth();
292
            $scalingFactorY = $diagramBoundsImage->getWidth() / $diagramBoundsXml->getWidth();
293
294
            $elementBoundsForImage = new DiagramNode($elementBounds->getId());
295
            $elementBoundsForImage->setX(round(($elementBounds->getX() - $diagramBoundsXml->getX()) * $scalingFactorX + $diagramBoundsImage->getX()));
296
            $elementBoundsForImage->setY(round(($elementBounds->getY() - $diagramBoundsXml->getY()) * $scalingFactorY + $diagramBoundsImage->getY()));
297
            $elementBoundsForImage->setWidth(round($elementBounds->getWidth() * $scalingFactorX));
298
            $elementBoundsForImage->setHeight(round($elementBounds->getHeight() * $scalingFactorY));
299
            return $elementBoundsForImage;
300
        } elseif (is_array($elementBounds)) {
301
            $listOfBoundsForImage = [];
302
            foreach ($elementBounds as $key => $value) {
303
                $listOfBoundsForImage[$key] = $this->transformBoundsForImage($diagramBoundsImage, $diagramBoundsXml, $value);
304
            }
305
            return $listOfBoundsForImage;
306
        }
307
    }
308
}
309