Completed
Push — master ( d8550b...8248af )
by Maurício
21s queued 16s
created

ShapeRecord::loadFromFile()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 42
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 6.0131

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
eloc 26
nc 9
nop 2
dl 0
loc 42
ccs 26
cts 28
cp 0.9286
crap 6.0131
rs 8.8817
c 3
b 0
f 0
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 strlen;
35
36
/**
37
 * ShapeFile record class.
38
 */
39
class ShapeRecord
40
{
41
    /** @var resource */
42
    private $shpFile;
43
44
    private ShapeFile|null $shapeFile = null;
45
46
    private int $size = 0;
47
48
    private int $read = 0;
49
50
    public int $recordNumber = 0;
51
52
    public string $lastError = '';
53
54
    /** @var mixed[] */
55
    public array $shpData = [];
56
57
    /** @var mixed[] */
58
    public array $dbfData = [];
59
60 83
    public function __construct(public ShapeType $shapeType)
61
    {
62 83
    }
63
64
    /**
65
     * Loads record from files.
66
     *
67
     * @param ShapeFile      $shapeFile The ShapeFile object
68
     * @param resource|false $dbfFile   Opened DBF file
69
     */
70 79
    public function loadFromFile(ShapeFile $shapeFile, $dbfFile): void
71
    {
72 79
        $this->shapeFile = $shapeFile;
73 79
        $this->loadHeaders();
74
75
        /* No header read */
76 79
        if ($this->read === 0) {
77 79
            return;
78
        }
79
80 79
        match ($this->shapeType) {
81 79
            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...
82 79
            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...
83 63
            ShapeType::PointM => $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...
84 59
            ShapeType::PointZ => $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...
85 55
            ShapeType::PolyLine => $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...
86 48
            ShapeType::PolyLineM => $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...
87 44
            ShapeType::PolyLineZ => $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...
88 40
            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...
89 28
            ShapeType::PolygonM => $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...
90 24
            ShapeType::PolygonZ => $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...
91 16
            ShapeType::MultiPoint => $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...
92 12
            ShapeType::MultiPointM => $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...
93 8
            ShapeType::MultiPointZ => $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...
94
            default => $this->reportInvalidShapeTypeError(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->reportInvalidShapeTypeError() targeting PhpMyAdmin\ShapeFile\Sha...InvalidShapeTypeError() 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 79
        };
96
97
        /* We need to skip rest of the record */
98 79
        while ($this->read < $this->size) {
99 24
            $this->loadData('V', 4);
100
        }
101
102
        /* Check if we didn't read too much */
103 79
        if ($this->read !== $this->size) {
104
            $this->reportInvalidShapeTypeError();
105
        }
106
107 79
        if (! ShapeFile::supportsDbase() || $dbfFile === false) {
108 57
            return;
109
        }
110
111 22
        $this->loadDBFData($dbfFile);
112
    }
113
114
    /**
115
     * Saves record to files.
116
     *
117
     * @param resource       $shpFile      Opened SHP file
118
     * @param resource|false $dbfFile      Opened DBF file
119
     * @param int            $recordNumber Record number
120
     */
121 51
    public function saveToFile($shpFile, $dbfFile, int $recordNumber): void
122
    {
123 51
        $this->shpFile = $shpFile;
124 51
        $this->recordNumber = $recordNumber;
125 51
        $this->saveHeaders();
126
127 51
        match ($this->shapeType) {
128 51
            ShapeType::Null => null, // Nothing to save
129 51
            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...
130 47
            ShapeType::PointM => $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...
131 43
            ShapeType::PointZ => $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...
132 39
            ShapeType::PolyLine => $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...
133 32
            ShapeType::PolyLineM => $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...
134 28
            ShapeType::PolyLineZ => $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...
135 24
            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...
136 20
            ShapeType::PolygonM => $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...
137 16
            ShapeType::PolygonZ => $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...
138 12
            ShapeType::MultiPoint => $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...
139 8
            ShapeType::MultiPointM => $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...
140 4
            ShapeType::MultiPointZ => $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...
141
            default => $this->reportInvalidShapeTypeError(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->reportInvalidShapeTypeError() targeting PhpMyAdmin\ShapeFile\Sha...InvalidShapeTypeError() 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 51
        };
143
144 51
        if (! ShapeFile::supportsDbase() || $dbfFile === false) {
145 36
            return;
146
        }
147
148 15
        $this->saveDBFData($dbfFile);
149
    }
150
151
    /**
152
     * Updates DBF data to match header.
153
     *
154
     * @param mixed[] $header DBF structure header
155
     */
156 51
    public function updateDBFInfo(array $header): void
157
    {
158 51
        $tmp = $this->dbfData;
159 51
        $this->dbfData = [];
160 51
        foreach ($header as [$value]) {
161 51
            $this->dbfData[$value] = $tmp[$value] ?? '';
162
        }
163
    }
164
165
    /**
166
     * Reads data.
167
     *
168
     * @param string      $type  type for unpack()
169
     * @param int<0, max> $count number of bytes
170
     */
171 79
    private function loadData(string $type, int $count): mixed
172
    {
173 79
        $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

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

786
        $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...
787 22
        unset($this->dbfData['deleted']);
788
    }
789
790
    /** @param resource $dbfFile */
791 15
    private function saveDBFData($dbfFile): void
792
    {
793 15
        if ($this->dbfData === []) {
794
            return;
795
        }
796
797 15
        unset($this->dbfData['deleted']);
798 15
        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

798
        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...
799
            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

799
            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...
800
                $this->setError("I wasn't possible to update the information in the DBF file.");
801
            }
802 15
        } 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

802
        } 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...
803
            $this->setError("I wasn't possible to add the information to the DBF file.");
804
        }
805
    }
806
807
    /**
808
     * Sets error message.
809
     */
810
    public function setError(string $error): void
811
    {
812
        $this->lastError = $error;
813
    }
814
815
    /**
816
     * Returns shape name.
817
     *
818
     * @psalm-return non-empty-string
819
     */
820 4
    public function getShapeName(): string
821
    {
822 4
        return ShapeType::name($this->shapeType);
823
    }
824
825
    private function reportInvalidShapeTypeError(): void
826
    {
827
        $this->setError('Invalid Shape type.');
828
    }
829
}
830