ShapeRecord   F
last analyzed

Complexity

Total Complexity 147

Size/Duplication

Total Lines 784
Duplicated Lines 0 %

Test Coverage

Coverage 93.31%

Importance

Changes 13
Bugs 0 Features 0
Metric Value
eloc 353
c 13
b 0
f 0
dl 0
loc 784
ccs 335
cts 359
cp 0.9331
rs 2
wmc 147

52 Methods

Rating   Name   Duplication   Size   Complexity  
A loadHeaders() 0 24 4
A savePolygonRecord() 0 3 1
A savePointRecord() 0 3 1
A loadPolyLineMZRecord() 0 20 6
A loadPolygonRecord() 0 3 1
B adjustBBox() 0 19 7
A setError() 0 3 1
A savePolyLineZRecord() 0 6 1
A savePolygonZRecord() 0 3 1
A savePolyLineRecord() 0 21 4
A loadPoint() 0 5 1
A loadPointZRecord() 0 3 1
A savePoint() 0 4 1
A loadData() 0 10 2
A loadNullRecord() 0 3 1
A loadPolygonZRecord() 0 3 1
A saveMultiPointRecord() 0 14 2
A savePolyLineMRecord() 0 5 1
A saveHeaders() 0 5 1
A loadPolygonMRecord() 0 3 1
A savePointZ() 0 6 1
B loadPolyLineRecord() 0 29 7
A savePointZRecord() 0 3 1
A loadPolyLineMRecord() 0 5 1
A saveMultiPointZRecord() 0 6 1
A savePolygonMRecord() 0 3 1
A loadPointM() 0 7 1
A savePolyLineMZRecord() 0 7 3
B loadFromFile() 0 42 6
A saveMultiPointMRecord() 0 5 1
A loadMultiPointRecord() 0 9 2
A loadPointRecord() 0 3 1
A __construct() 0 2 1
A loadMultiPointMZRecord() 0 12 4
A savePointM() 0 5 1
A loadPointMRecord() 0 3 1
A loadMultiPointZRecord() 0 6 1
A saveDBFData() 0 13 5
A loadPolyLineZRecord() 0 6 1
A saveToFile() 0 28 3
A updateDBFInfo() 0 6 2
A saveMultiPointMZRecord() 0 6 2
A loadMultiPointMRecord() 0 5 1
A loadPointZ() 0 8 1
A loadBBox() 0 6 1
A loadDBFData() 0 4 1
A savePointMRecord() 0 3 1
D deletePoint() 0 65 21
A adjustPoint() 0 11 3
A getShapeName() 0 3 1
C getContentLength() 0 60 17
C addPoint() 0 39 14

How to fix   Complexity   

Complex Class

Complex classes like ShapeRecord often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ShapeRecord, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * phpMyAdmin ShapeFile library
7
 * <https://github.com/phpmyadmin/shapefile/>.
8
 *
9
 * Copyright 2006-2007 Ovidio <ovidio AT users.sourceforge.net>
10
 * Copyright 2016 - 2017 Michal Čihař <[email protected]>
11
 *
12
 * This program is free software; you can redistribute it and/or
13
 * modify it under the terms of the GNU General Public License
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 * along with this program; if not, you can download one from
23
 * https://www.gnu.org/copyleft/gpl.html.
24
 */
25
26
namespace PhpMyAdmin\ShapeFile;
27
28
use function array_values;
29
use function count;
30
use function fwrite;
31
use function in_array;
32
use function is_array;
33
use function pack;
34
use function sprintf;
35
use function strlen;
36
37
/**
38
 * ShapeFile record class.
39
 */
40
class ShapeRecord
41
{
42
    /** @var resource */
43
    private $shpFile;
44
45
    private ShapeFile|null $shapeFile = null;
46
47
    private int $size = 0;
48
49
    private int $read = 0;
50
51
    public int $recordNumber = 0;
52
53
    public string $lastError = '';
54
55
    /** @var mixed[] */
56
    public array $shpData = [];
57
58
    /** @var mixed[] */
59
    public array $dbfData = [];
60
61 86
    public function __construct(public int $shapeType)
62
    {
63 86
    }
64
65
    /**
66
     * Loads record from files.
67
     *
68
     * @param ShapeFile      $shapeFile The ShapeFile object
69
     * @param resource|false $dbfFile   Opened DBF file
70
     */
71 82
    public function loadFromFile(ShapeFile $shapeFile, $dbfFile): void
72
    {
73 82
        $this->shapeFile = $shapeFile;
74 82
        $this->loadHeaders();
75
76
        /* No header read */
77 82
        if ($this->read === 0) {
78 82
            return;
79
        }
80
81 82
        match ($this->shapeType) {
82 82
            ShapeType::NULL => $this->loadNullRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadNullRecord() targeting PhpMyAdmin\ShapeFile\ShapeRecord::loadNullRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
83 82
            ShapeType::POINT => $this->loadPointRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPointRecord() targeting PhpMyAdmin\ShapeFile\Sha...cord::loadPointRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
84 82
            ShapeType::POINT_M => $this->loadPointMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPointMRecord() targeting PhpMyAdmin\ShapeFile\Sha...ord::loadPointMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
85 82
            ShapeType::POINT_Z => $this->loadPointZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPointZRecord() targeting PhpMyAdmin\ShapeFile\Sha...ord::loadPointZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
86 82
            ShapeType::POLY_LINE => $this->loadPolyLineRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolyLineRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::loadPolyLineRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
87 82
            ShapeType::POLY_LINE_M => $this->loadPolyLineMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolyLineMRecord() targeting PhpMyAdmin\ShapeFile\Sha...::loadPolyLineMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
88 82
            ShapeType::POLY_LINE_Z => $this->loadPolyLineZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolyLineZRecord() targeting PhpMyAdmin\ShapeFile\Sha...::loadPolyLineZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
89 82
            ShapeType::POLYGON => $this->loadPolygonRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolygonRecord() targeting PhpMyAdmin\ShapeFile\Sha...rd::loadPolygonRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
90 82
            ShapeType::POLYGON_M => $this->loadPolygonMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolygonMRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::loadPolygonMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
91 82
            ShapeType::POLYGON_Z => $this->loadPolygonZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadPolygonZRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::loadPolygonZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
92 82
            ShapeType::MULTI_POINT => $this->loadMultiPointRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadMultiPointRecord() targeting PhpMyAdmin\ShapeFile\Sha...:loadMultiPointRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
93 82
            ShapeType::MULTI_POINT_M => $this->loadMultiPointMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadMultiPointMRecord() targeting PhpMyAdmin\ShapeFile\Sha...loadMultiPointMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
94 82
            ShapeType::MULTI_POINT_Z => $this->loadMultiPointZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->loadMultiPointZRecord() targeting PhpMyAdmin\ShapeFile\Sha...loadMultiPointZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
95 82
            default => $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setError(sprintf(...d.', $this->shapeType)) targeting PhpMyAdmin\ShapeFile\ShapeRecord::setError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
96 82
        };
97
98
        /* We need to skip rest of the record */
99 82
        while ($this->read < $this->size) {
100 24
            $this->loadData('V', 4);
101
        }
102
103
        /* Check if we didn't read too much */
104 82
        if ($this->read !== $this->size) {
105
            $this->setError(sprintf('Failed to parse record, read=%d, size=%d', $this->read, $this->size));
106
        }
107
108 82
        if (! ShapeFile::supportsDbase() || $dbfFile === false) {
109 38
            return;
110
        }
111
112 44
        $this->loadDBFData($dbfFile);
113
    }
114
115
    /**
116
     * Saves record to files.
117
     *
118
     * @param resource       $shpFile      Opened SHP file
119
     * @param resource|false $dbfFile      Opened DBF file
120
     * @param int            $recordNumber Record number
121
     */
122 54
    public function saveToFile($shpFile, $dbfFile, int $recordNumber): void
123
    {
124 54
        $this->shpFile = $shpFile;
125 54
        $this->recordNumber = $recordNumber;
126 54
        $this->saveHeaders();
127
128 54
        match ($this->shapeType) {
129 54
            ShapeType::NULL => null, // Nothing to save
130 54
            ShapeType::POINT => $this->savePointRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePointRecord() targeting PhpMyAdmin\ShapeFile\Sha...cord::savePointRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
131 54
            ShapeType::POINT_M => $this->savePointMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePointMRecord() targeting PhpMyAdmin\ShapeFile\Sha...ord::savePointMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
132 54
            ShapeType::POINT_Z => $this->savePointZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePointZRecord() targeting PhpMyAdmin\ShapeFile\Sha...ord::savePointZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
133 54
            ShapeType::POLY_LINE => $this->savePolyLineRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolyLineRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::savePolyLineRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
134 54
            ShapeType::POLY_LINE_M => $this->savePolyLineMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolyLineMRecord() targeting PhpMyAdmin\ShapeFile\Sha...::savePolyLineMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
135 54
            ShapeType::POLY_LINE_Z => $this->savePolyLineZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolyLineZRecord() targeting PhpMyAdmin\ShapeFile\Sha...::savePolyLineZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
136 54
            ShapeType::POLYGON => $this->savePolygonRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolygonRecord() targeting PhpMyAdmin\ShapeFile\Sha...rd::savePolygonRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
137 54
            ShapeType::POLYGON_M => $this->savePolygonMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolygonMRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::savePolygonMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
138 54
            ShapeType::POLYGON_Z => $this->savePolygonZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->savePolygonZRecord() targeting PhpMyAdmin\ShapeFile\Sha...d::savePolygonZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
139 54
            ShapeType::MULTI_POINT => $this->saveMultiPointRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->saveMultiPointRecord() targeting PhpMyAdmin\ShapeFile\Sha...:saveMultiPointRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
140 54
            ShapeType::MULTI_POINT_M => $this->saveMultiPointMRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->saveMultiPointMRecord() targeting PhpMyAdmin\ShapeFile\Sha...saveMultiPointMRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
141 54
            ShapeType::MULTI_POINT_Z => $this->saveMultiPointZRecord(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->saveMultiPointZRecord() targeting PhpMyAdmin\ShapeFile\Sha...saveMultiPointZRecord() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
142 54
            default => $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType)),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->setError(sprintf(...d.', $this->shapeType)) targeting PhpMyAdmin\ShapeFile\ShapeRecord::setError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
143 54
        };
144
145 54
        if (! ShapeFile::supportsDbase() || $dbfFile === false) {
146 24
            return;
147
        }
148
149 30
        $this->saveDBFData($dbfFile);
150
    }
151
152
    /**
153
     * Updates DBF data to match header.
154
     *
155
     * @param mixed[] $header DBF structure header
156
     */
157 54
    public function updateDBFInfo(array $header): void
158
    {
159 54
        $tmp = $this->dbfData;
160 54
        $this->dbfData = [];
161 54
        foreach ($header as [$value]) {
162 54
            $this->dbfData[$value] = $tmp[$value] ?? '';
163
        }
164
    }
165
166
    /**
167
     * Reads data.
168
     *
169
     * @param string      $type  type for unpack()
170
     * @param int<0, max> $count number of bytes
171
     */
172 82
    private function loadData(string $type, int $count): mixed
173
    {
174 82
        $data = $this->shapeFile->readSHP($count);
0 ignored issues
show
Bug introduced by
The method readSHP() does not exist on null. ( Ignorable by Annotation )

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

174
        /** @scrutinizer ignore-call */ 
175
        $data = $this->shapeFile->readSHP($count);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
175 82
        if ($data === false) {
176
            return false;
177
        }
178
179 82
        $this->read += strlen($data);
180
181 82
        return Util::loadData($type, $data);
182
    }
183
184
    /**
185
     * Loads metadata header from a file.
186
     */
187 82
    private function loadHeaders(): void
188
    {
189 82
        $this->shapeType = -1;
190 82
        $recordNumber = $this->loadData('N', 4);
191 82
        if ($recordNumber === false) {
192 82
            return;
193
        }
194
195 82
        $this->recordNumber = (int) $recordNumber;
196
197
        // We read the length of the record
198 82
        $size = $this->loadData('N', 4);
199 82
        if ($size === false) {
200
            return;
201
        }
202
203 82
        $this->size = ($size * 2) + 8;
204
205 82
        $shapeType = $this->loadData('V', 4);
206 82
        if ($shapeType === false) {
207
            return;
208
        }
209
210 82
        $this->shapeType = (int) $shapeType;
211
    }
212
213
    /**
214
     * Saves metadata header to a file.
215
     */
216 54
    private function saveHeaders(): void
217
    {
218 54
        fwrite($this->shpFile, pack('N', $this->recordNumber));
219 54
        fwrite($this->shpFile, pack('N', $this->getContentLength()));
220 54
        fwrite($this->shpFile, pack('V', $this->shapeType));
221
    }
222
223
    /** @return mixed[] */
224 82
    private function loadPoint(): array
225
    {
226 82
        return [
227 82
            'x' => $this->loadData('d', 8),
228 82
            'y' => $this->loadData('d', 8),
229 82
        ];
230
    }
231
232
    /** @return mixed[] */
233 10
    private function loadPointM(): array
234
    {
235 10
        $data = $this->loadPoint();
236
237 10
        $data['m'] = $this->loadData('d', 8);
238
239 10
        return $data;
240
    }
241
242
    /** @return mixed[] */
243 10
    private function loadPointZ(): array
244
    {
245 10
        $data = $this->loadPoint();
246
247 10
        $data['z'] = $this->loadData('d', 8);
248 10
        $data['m'] = $this->loadData('d', 8);
249
250 10
        return $data;
251
    }
252
253
    /** @param mixed[] $data */
254 46
    private function savePoint(array $data): void
255
    {
256 46
        fwrite($this->shpFile, Util::packDouble($data['x']));
257 46
        fwrite($this->shpFile, Util::packDouble($data['y']));
258
    }
259
260
    /** @param mixed[] $data */
261 10
    private function savePointM(array $data): void
262
    {
263 10
        fwrite($this->shpFile, Util::packDouble($data['x']));
264 10
        fwrite($this->shpFile, Util::packDouble($data['y']));
265 10
        fwrite($this->shpFile, Util::packDouble($data['m']));
266
    }
267
268
    /** @param mixed[] $data */
269 10
    private function savePointZ(array $data): void
270
    {
271 10
        fwrite($this->shpFile, Util::packDouble($data['x']));
272 10
        fwrite($this->shpFile, Util::packDouble($data['y']));
273 10
        fwrite($this->shpFile, Util::packDouble($data['z']));
274 10
        fwrite($this->shpFile, Util::packDouble($data['m']));
275
    }
276
277
    private function loadNullRecord(): void
278
    {
279
        $this->shpData = [];
280
    }
281
282 22
    private function loadPointRecord(): void
283
    {
284 22
        $this->shpData = $this->loadPoint();
285
    }
286
287 10
    private function loadPointMRecord(): void
288
    {
289 10
        $this->shpData = $this->loadPointM();
290
    }
291
292 10
    private function loadPointZRecord(): void
293
    {
294 10
        $this->shpData = $this->loadPointZ();
295
    }
296
297 10
    private function savePointRecord(): void
298
    {
299 10
        $this->savePoint($this->shpData);
300
    }
301
302 10
    private function savePointMRecord(): void
303
    {
304 10
        $this->savePointM($this->shpData);
305
    }
306
307 10
    private function savePointZRecord(): void
308
    {
309 10
        $this->savePointZ($this->shpData);
310
    }
311
312 58
    private function loadBBox(): void
313
    {
314 58
        $this->shpData['xmin'] = $this->loadData('d', 8);
315 58
        $this->shpData['ymin'] = $this->loadData('d', 8);
316 58
        $this->shpData['xmax'] = $this->loadData('d', 8);
317 58
        $this->shpData['ymax'] = $this->loadData('d', 8);
318
    }
319
320 16
    private function loadMultiPointRecord(): void
321
    {
322 16
        $this->shpData = [];
323 16
        $this->loadBBox();
324
325 16
        $this->shpData['numpoints'] = $this->loadData('V', 4);
326
327 16
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
328 16
            $this->shpData['points'][] = $this->loadPoint();
329
        }
330
    }
331
332 12
    private function loadMultiPointMZRecord(string $type): void
333
    {
334
        /* The m dimension is optional, depends on bounding box data */
335 12
        if ($type === 'm' && ! $this->shapeFile->hasMeasure()) {
336 12
            return;
337
        }
338
339 8
        $this->shpData[$type . 'min'] = $this->loadData('d', 8);
340 8
        $this->shpData[$type . 'max'] = $this->loadData('d', 8);
341
342 8
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
343 8
            $this->shpData['points'][$i][$type] = $this->loadData('d', 8);
344
        }
345
    }
346
347 4
    private function loadMultiPointMRecord(): void
348
    {
349 4
        $this->loadMultiPointRecord();
350
351 4
        $this->loadMultiPointMZRecord('m');
352
    }
353
354 8
    private function loadMultiPointZRecord(): void
355
    {
356 8
        $this->loadMultiPointRecord();
357
358 8
        $this->loadMultiPointMZRecord('z');
359 8
        $this->loadMultiPointMZRecord('m');
360
    }
361
362 12
    private function saveMultiPointRecord(): void
363
    {
364 12
        fwrite($this->shpFile, pack(
365 12
            'dddd',
366 12
            $this->shpData['xmin'],
367 12
            $this->shpData['ymin'],
368 12
            $this->shpData['xmax'],
369 12
            $this->shpData['ymax'],
370 12
        ));
371
372 12
        fwrite($this->shpFile, pack('V', $this->shpData['numpoints']));
373
374 12
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
375 12
            $this->savePoint($this->shpData['points'][$i]);
376
        }
377
    }
378
379 8
    private function saveMultiPointMZRecord(string $type): void
380
    {
381 8
        fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
382
383 8
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
384 8
            fwrite($this->shpFile, Util::packDouble($this->shpData['points'][$i][$type]));
385
        }
386
    }
387
388 4
    private function saveMultiPointMRecord(): void
389
    {
390 4
        $this->saveMultiPointRecord();
391
392 4
        $this->saveMultiPointMZRecord('m');
393
    }
394
395 4
    private function saveMultiPointZRecord(): void
396
    {
397 4
        $this->saveMultiPointRecord();
398
399 4
        $this->saveMultiPointMZRecord('z');
400 4
        $this->saveMultiPointMZRecord('m');
401
    }
402
403 42
    private function loadPolyLineRecord(): void
404
    {
405 42
        $this->shpData = [];
406 42
        $this->loadBBox();
407
408 42
        $this->shpData['numparts'] = $this->loadData('V', 4);
409 42
        $this->shpData['numpoints'] = $this->loadData('V', 4);
410
411 42
        $numparts = $this->shpData['numparts'];
412 42
        $numpoints = $this->shpData['numpoints'];
413
414 42
        for ($i = 0; $i < $numparts; ++$i) {
415 42
            $this->shpData['parts'][$i] = $this->loadData('V', 4);
416
        }
417
418 42
        $part = 0;
419 42
        for ($i = 0; $i < $numpoints; ++$i) {
420 42
            if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) {
421 38
                ++$part;
422
            }
423
424
            if (
425 42
                ! isset($this->shpData['parts'][$part]['points'])
426 42
                || ! is_array($this->shpData['parts'][$part]['points'])
427
            ) {
428 42
                $this->shpData['parts'][$part] = ['points' => []];
429
            }
430
431 42
            $this->shpData['parts'][$part]['points'][] = $this->loadPoint();
432
        }
433
    }
434
435 20
    private function loadPolyLineMZRecord(string $type): void
436
    {
437
        /* The m dimension is optional, depends on bounding box data */
438 20
        if ($type === 'm' && ! $this->shapeFile->hasMeasure()) {
439 20
            return;
440
        }
441
442 12
        $this->shpData[$type . 'min'] = $this->loadData('d', 8);
443 12
        $this->shpData[$type . 'max'] = $this->loadData('d', 8);
444
445 12
        $numparts = $this->shpData['numparts'];
446 12
        $numpoints = $this->shpData['numpoints'];
447
448 12
        $part = 0;
449 12
        for ($i = 0; $i < $numpoints; ++$i) {
450 12
            if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) {
451
                ++$part;
452
            }
453
454 12
            $this->shpData['parts'][$part]['points'][$i][$type] = $this->loadData('d', 8);
455
        }
456
    }
457
458 8
    private function loadPolyLineMRecord(): void
459
    {
460 8
        $this->loadPolyLineRecord();
461
462 8
        $this->loadPolyLineMZRecord('m');
463
    }
464
465 12
    private function loadPolyLineZRecord(): void
466
    {
467 12
        $this->loadPolyLineRecord();
468
469 12
        $this->loadPolyLineMZRecord('z');
470 12
        $this->loadPolyLineMZRecord('m');
471
    }
472
473 30
    private function savePolyLineRecord(): void
474
    {
475 30
        fwrite($this->shpFile, pack(
476 30
            'dddd',
477 30
            $this->shpData['xmin'],
478 30
            $this->shpData['ymin'],
479 30
            $this->shpData['xmax'],
480 30
            $this->shpData['ymax'],
481 30
        ));
482
483 30
        fwrite($this->shpFile, pack('VV', $this->shpData['numparts'], $this->shpData['numpoints']));
484
485 30
        $partIndex = 0;
486 30
        for ($i = 0; $i < $this->shpData['numparts']; ++$i) {
487 30
            fwrite($this->shpFile, pack('V', $partIndex));
488 30
            $partIndex += count($this->shpData['parts'][$i]['points']);
489
        }
490
491 30
        foreach ($this->shpData['parts'] as $partData) {
492 30
            foreach ($partData['points'] as $pointData) {
493 30
                $this->savePoint($pointData);
494
            }
495
        }
496
    }
497
498 16
    private function savePolyLineMZRecord(string $type): void
499
    {
500 16
        fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
501
502 16
        foreach ($this->shpData['parts'] as $partData) {
503 16
            foreach ($partData['points'] as $pointData) {
504 16
                fwrite($this->shpFile, Util::packDouble($pointData[$type]));
505
            }
506
        }
507
    }
508
509 8
    private function savePolyLineMRecord(): void
510
    {
511 8
        $this->savePolyLineRecord();
512
513 8
        $this->savePolyLineMZRecord('m');
514
    }
515
516 8
    private function savePolyLineZRecord(): void
517
    {
518 8
        $this->savePolyLineRecord();
519
520 8
        $this->savePolyLineMZRecord('z');
521 8
        $this->savePolyLineMZRecord('m');
522
    }
523
524 12
    private function loadPolygonRecord(): void
525
    {
526 12
        $this->loadPolyLineRecord();
527
    }
528
529 4
    private function loadPolygonMRecord(): void
530
    {
531 4
        $this->loadPolyLineMRecord();
532
    }
533
534 8
    private function loadPolygonZRecord(): void
535
    {
536 8
        $this->loadPolyLineZRecord();
537
    }
538
539 4
    private function savePolygonRecord(): void
540
    {
541 4
        $this->savePolyLineRecord();
542
    }
543
544 4
    private function savePolygonMRecord(): void
545
    {
546 4
        $this->savePolyLineMRecord();
547
    }
548
549 4
    private function savePolygonZRecord(): void
550
    {
551 4
        $this->savePolyLineZRecord();
552
    }
553
554
    /** @param mixed[] $point */
555 54
    private function adjustBBox(array $point): void
556
    {
557
        // Adjusts bounding box based on point
558 54
        foreach (['x', 'y', 'z', 'm'] as $direction) {
559 54
            if (! isset($point[$direction])) {
560 38
                continue;
561
            }
562
563 54
            $min = $direction . 'min';
564 54
            $max = $direction . 'max';
565 54
            if (! isset($this->shpData[$min]) || ($this->shpData[$min] > $point[$direction])) {
566 54
                $this->shpData[$min] = $point[$direction];
567
            }
568
569 54
            if (isset($this->shpData[$max]) && ($this->shpData[$max] >= $point[$direction])) {
570 42
                continue;
571
            }
572
573 54
            $this->shpData[$max] = $point[$direction];
574
        }
575
    }
576
577
    /**
578
     * Adjust point and bounding box when adding point.
579
     * Sets dimension to 0 if not set.
580
     *
581
     * @param mixed[] $point Point data
582
     *
583
     * @return mixed[] Fixed point data
584
     */
585 54
    private function adjustPoint(array $point): array
586
    {
587 54
        if (in_array($this->shapeType, ShapeType::MEASURED_TYPES, true)) {
588 38
            $point['m'] ??= 0.0;
589
        }
590
591 54
        if (in_array($this->shapeType, ShapeType::TYPES_WITH_Z, true)) {
592 22
            $point['z'] ??= 0.0;
593
        }
594
595 54
        return $point;
596
    }
597
598
    /**
599
     * Adds point to a record.
600
     *
601
     * @param mixed[] $point     Point data
602
     * @param int     $partIndex Part index
603
     */
604 54
    public function addPoint(array $point, int $partIndex = 0): void
605
    {
606 54
        $point = $this->adjustPoint($point);
607 54
        switch ($this->shapeType) {
608
            case ShapeType::NULL:
609
                //Don't add anything
610
                return;
611
612
            case ShapeType::POINT:
613
            case ShapeType::POINT_Z:
614
            case ShapeType::POINT_M:
615
                //Substitutes the value of the current point
616 18
                $this->shpData = $point;
617 18
                break;
618
            case ShapeType::POLY_LINE:
619
            case ShapeType::POLYGON:
620
            case ShapeType::POLY_LINE_Z:
621
            case ShapeType::POLYGON_Z:
622
            case ShapeType::POLY_LINE_M:
623
            case ShapeType::POLYGON_M:
624
                //Adds a new point to the selected part
625 30
                $this->shpData['parts'][$partIndex]['points'][] = $point;
626 30
                $this->shpData['numparts'] = count($this->shpData['parts']);
627 30
                $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0);
628 30
                break;
629
            case ShapeType::MULTI_POINT:
630
            case ShapeType::MULTI_POINT_Z:
631
            case ShapeType::MULTI_POINT_M:
632
                //Adds a new point
633 12
                $this->shpData['points'][] = $point;
634 12
                $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0);
635 12
                break;
636
            default:
637
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
638
639
                return;
640
        }
641
642 54
        $this->adjustBBox($point);
643
    }
644
645
    /**
646
     * Deletes point from a record.
647
     *
648
     * @param int $pointIndex Point index
649
     * @param int $partIndex  Part index
650
     */
651 48
    public function deletePoint(int $pointIndex = 0, int $partIndex = 0): void
652
    {
653 48
        switch ($this->shapeType) {
654
            case ShapeType::NULL:
655
                //Don't delete anything
656
                break;
657
            case ShapeType::POINT:
658
            case ShapeType::POINT_Z:
659
            case ShapeType::POINT_M:
660
                //Sets the value of the point to zero
661 12
                $this->shpData['x'] = 0.0;
662 12
                $this->shpData['y'] = 0.0;
663 12
                if (in_array($this->shapeType, [ShapeType::POINT_Z, ShapeType::POINT_M], true)) {
664 8
                    $this->shpData['m'] = 0.0;
665
                }
666
667 12
                if ($this->shapeType === ShapeType::POINT_Z) {
668 4
                    $this->shpData['z'] = 0.0;
669
                }
670
671 12
                break;
672
            case ShapeType::POLY_LINE:
673
            case ShapeType::POLYGON:
674
            case ShapeType::POLY_LINE_Z:
675
            case ShapeType::POLYGON_Z:
676
            case ShapeType::POLY_LINE_M:
677
            case ShapeType::POLYGON_M:
678
                //Deletes the point from the selected part, if exists
679
                if (
680 24
                    isset($this->shpData['parts'][$partIndex])
681 24
                    && isset($this->shpData['parts'][$partIndex]['points'][$pointIndex])
682
                ) {
683 24
                    $count = count($this->shpData['parts'][$partIndex]['points']) - 1;
684 24
                    for ($i = $pointIndex; $i < $count; ++$i) {
685 24
                        $point = $this->shpData['parts'][$partIndex]['points'][$i + 1];
686 24
                        $this->shpData['parts'][$partIndex]['points'][$i] = $point;
687
                    }
688
689 24
                    $count = count($this->shpData['parts'][$partIndex]['points']) - 1;
690 24
                    unset($this->shpData['parts'][$partIndex]['points'][$count]);
691
692 24
                    $this->shpData['numparts'] = count($this->shpData['parts']);
693 24
                    --$this->shpData['numpoints'];
694
                }
695
696 24
                break;
697
            case ShapeType::MULTI_POINT:
698
            case ShapeType::MULTI_POINT_Z:
699
            case ShapeType::MULTI_POINT_M:
700
                //Deletes the point, if exists
701 12
                if (isset($this->shpData['points'][$pointIndex])) {
702 12
                    $count = count($this->shpData['points']) - 1;
703 12
                    for ($i = $pointIndex; $i < $count; ++$i) {
704 12
                        $this->shpData['points'][$i] = $this->shpData['points'][$i + 1];
705
                    }
706
707 12
                    unset($this->shpData['points'][count($this->shpData['points']) - 1]);
708
709 12
                    --$this->shpData['numpoints'];
710
                }
711
712 12
                break;
713
            default:
714
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
715
                break;
716
        }
717
    }
718
719
    /**
720
     * Returns length of content.
721
     */
722 54
    public function getContentLength(): int|null
723
    {
724
        // The content length for a record is the length of the record contents section measured in 16-bit words.
725
        // one coordinate makes 4 16-bit words (64 bit double)
726 54
        switch ($this->shapeType) {
727
            case ShapeType::NULL:
728
                $result = 0;
729
                break;
730
            case ShapeType::POINT:
731 10
                $result = 10;
732 10
                break;
733
            case ShapeType::POINT_M:
734 10
                $result = 10 + 4;
735 10
                break;
736
            case ShapeType::POINT_Z:
737 10
                $result = 10 + 8;
738 10
                break;
739
            case ShapeType::POLY_LINE:
740
            case ShapeType::POLYGON:
741 14
                $count = count($this->shpData['parts']);
742 14
                $result = 22 + 2 * $count;
743 14
                for ($i = 0; $i < $count; ++$i) {
744 14
                    $result += 8 * count($this->shpData['parts'][$i]['points']);
745
                }
746
747 14
                break;
748
            case ShapeType::POLY_LINE_M:
749
            case ShapeType::POLYGON_M:
750 8
                $count = count($this->shpData['parts']);
751 8
                $result = 22 + (2 * 4) + 2 * $count;
752 8
                for ($i = 0; $i < $count; ++$i) {
753 8
                    $result += (8 + 4) * count($this->shpData['parts'][$i]['points']);
754
                }
755
756 8
                break;
757
            case ShapeType::POLY_LINE_Z:
758
            case ShapeType::POLYGON_Z:
759 8
                $count = count($this->shpData['parts']);
760 8
                $result = 22 + (4 * 4) + 2 * $count;
761 8
                for ($i = 0; $i < $count; ++$i) {
762 8
                    $result += (8 + 8) * count($this->shpData['parts'][$i]['points']);
763
                }
764
765 8
                break;
766
            case ShapeType::MULTI_POINT:
767 4
                $result = 20 + 8 * count($this->shpData['points']);
768 4
                break;
769
            case ShapeType::MULTI_POINT_M:
770 4
                $result = 20 + (2 * 4) + (8 + 4) * count($this->shpData['points']);
771 4
                break;
772
            case ShapeType::MULTI_POINT_Z:
773 4
                $result = 20 + (4 * 4) + (8 + 8) * count($this->shpData['points']);
774 4
                break;
775
            default:
776
                $result = null;
777
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
778
                break;
779
        }
780
781 54
        return $result;
782
    }
783
784
    /** @param resource $dbfFile Opened DBF file */
785 44
    private function loadDBFData($dbfFile): void
786
    {
787 44
        $this->dbfData = @dbase_get_record_with_names($dbfFile, $this->recordNumber);
0 ignored issues
show
Bug introduced by
The function dbase_get_record_with_names was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

787
        $this->dbfData = @/** @scrutinizer ignore-call */ dbase_get_record_with_names($dbfFile, $this->recordNumber);
Loading history...
introduced by
Function dbase_get_record_with_names() should not be referenced via a fallback global name, but via a use statement.
Loading history...
788 44
        unset($this->dbfData['deleted']);
789
    }
790
791
    /** @param resource $dbfFile */
792 30
    private function saveDBFData($dbfFile): void
793
    {
794 30
        if ($this->dbfData === []) {
795
            return;
796
        }
797
798 30
        unset($this->dbfData['deleted']);
799 30
        if ($this->recordNumber <= dbase_numrecords($dbfFile)) {
0 ignored issues
show
Bug introduced by
The function dbase_numrecords was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

799
        if ($this->recordNumber <= /** @scrutinizer ignore-call */ dbase_numrecords($dbfFile)) {
Loading history...
introduced by
Function dbase_numrecords() should not be referenced via a fallback global name, but via a use statement.
Loading history...
800
            if (! dbase_replace_record($dbfFile, array_values($this->dbfData), $this->recordNumber)) {
0 ignored issues
show
Bug introduced by
The function dbase_replace_record was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

800
            if (! /** @scrutinizer ignore-call */ dbase_replace_record($dbfFile, array_values($this->dbfData), $this->recordNumber)) {
Loading history...
introduced by
Function dbase_replace_record() should not be referenced via a fallback global name, but via a use statement.
Loading history...
801
                $this->setError("I wasn't possible to update the information in the DBF file.");
802
            }
803 30
        } elseif (! dbase_add_record($dbfFile, array_values($this->dbfData))) {
0 ignored issues
show
Bug introduced by
The function dbase_add_record was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

803
        } elseif (! /** @scrutinizer ignore-call */ dbase_add_record($dbfFile, array_values($this->dbfData))) {
Loading history...
introduced by
Function dbase_add_record() should not be referenced via a fallback global name, but via a use statement.
Loading history...
804
            $this->setError("I wasn't possible to add the information to the DBF file.");
805
        }
806
    }
807
808
    /**
809
     * Sets error message.
810
     */
811
    public function setError(string $error): void
812
    {
813
        $this->lastError = $error;
814
    }
815
816
    /**
817
     * Returns shape name.
818
     *
819
     * @psalm-return non-empty-string
820
     */
821 4
    public function getShapeName(): string
822
    {
823 4
        return ShapeType::name($this->shapeType);
824
    }
825
}
826