Passed
Pull Request — master (#33)
by Maurício
23:35 queued 10:39
created

ShapeRecord::getContentLength()   C

Complexity

Conditions 17
Paths 14

Size

Total Lines 60
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 17.8433

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 17
eloc 48
c 2
b 0
f 0
nc 14
nop 0
dl 0
loc 60
ccs 30
cts 35
cp 0.8571
crap 17.8433
rs 5.2166

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
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 89
    public function __construct(public int $shapeType)
62
    {
63 89
    }
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 85
    public function loadFromFile(ShapeFile $shapeFile, $dbfFile): void
72
    {
73 85
        $this->shapeFile = $shapeFile;
74 85
        $this->loadHeaders();
75
76
        /* No header read */
77 85
        if ($this->read === 0) {
78 85
            return;
79
        }
80
81 85
        match ($this->shapeType) {
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 25
            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 13
            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 13
            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 13
            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 4
            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 4
            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 12
            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 4
            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 8
            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 4
            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 4
            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 8
            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
            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 85
        };
97
98
        /* We need to skip rest of the record */
99 85
        while ($this->read < $this->size) {
100 24
            $this->loadData('V', 4);
101
        }
102
103
        /* Check if we didn't read too much */
104 85
        if ($this->read !== $this->size) {
105
            $this->setError(sprintf('Failed to parse record, read=%d, size=%d', $this->read, $this->size));
106
        }
107
108 85
        if (! ShapeFile::supportsDbase() || $dbfFile === false) {
109 19
            return;
110
        }
111
112 66
        $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 57
    public function saveToFile($shpFile, $dbfFile, int $recordNumber): void
123
    {
124 57
        $this->shpFile = $shpFile;
125 57
        $this->recordNumber = $recordNumber;
126 57
        $this->saveHeaders();
127
128 57
        match ($this->shapeType) {
129
            ShapeType::NULL => null, // Nothing to save
130 13
            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 13
            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 13
            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 13
            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 4
            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 4
            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 4
            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 4
            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 4
            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 4
            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 4
            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 4
            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
            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 57
        };
144
145 57
        if (! ShapeFile::supportsDbase() || $dbfFile === false) {
146 12
            return;
147
        }
148
149 45
        $this->saveDBFData($dbfFile);
150
    }
151
152
    /**
153
     * Updates DBF data to match header.
154
     *
155
     * @param mixed[] $header DBF structure header
156
     */
157 57
    public function updateDBFInfo(array $header): void
158
    {
159 57
        $tmp = $this->dbfData;
160 57
        $this->dbfData = [];
161 57
        foreach ($header as [$value]) {
162 57
            $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 85
    private function loadData(string $type, int $count): mixed
173
    {
174 85
        $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 85
        if ($data === false) {
176
            return false;
177
        }
178
179 85
        $this->read += strlen($data);
180
181 85
        return Util::loadData($type, $data);
182
    }
183
184
    /**
185
     * Loads metadata header from a file.
186
     */
187 85
    private function loadHeaders(): void
188
    {
189 85
        $this->shapeType = -1;
190 85
        $recordNumber = $this->loadData('N', 4);
191 85
        if ($recordNumber === false) {
192 85
            return;
193
        }
194
195 85
        $this->recordNumber = (int) $recordNumber;
196
197
        // We read the length of the record
198 85
        $size = $this->loadData('N', 4);
199 85
        if ($size === false) {
200
            return;
201
        }
202
203 85
        $this->size = ($size * 2) + 8;
204
205 85
        $shapeType = $this->loadData('V', 4);
206 85
        if ($shapeType === false) {
207
            return;
208
        }
209
210 85
        $this->shapeType = (int) $shapeType;
211
    }
212
213
    /**
214
     * Saves metadata header to a file.
215
     */
216 57
    private function saveHeaders(): void
217
    {
218 57
        fwrite($this->shpFile, pack('N', $this->recordNumber));
219 57
        fwrite($this->shpFile, pack('N', $this->getContentLength()));
220 57
        fwrite($this->shpFile, pack('V', $this->shapeType));
221
    }
222
223
    /** @return mixed[] */
224 85
    private function loadPoint(): array
225
    {
226 85
        return [
227 85
            'x' => $this->loadData('d', 8),
228 85
            'y' => $this->loadData('d', 8),
229 85
        ];
230
    }
231
232
    /** @return mixed[] */
233 13
    private function loadPointM(): array
234
    {
235 13
        $data = $this->loadPoint();
236
237 13
        $data['m'] = $this->loadData('d', 8);
238
239 13
        return $data;
240
    }
241
242
    /** @return mixed[] */
243 13
    private function loadPointZ(): array
244
    {
245 13
        $data = $this->loadPoint();
246
247 13
        $data['z'] = $this->loadData('d', 8);
248 13
        $data['m'] = $this->loadData('d', 8);
249
250 13
        return $data;
251
    }
252
253
    /** @param mixed[] $data */
254 49
    private function savePoint(array $data): void
255
    {
256 49
        fwrite($this->shpFile, Util::packDouble($data['x']));
257 49
        fwrite($this->shpFile, Util::packDouble($data['y']));
258
    }
259
260
    /** @param mixed[] $data */
261 13
    private function savePointM(array $data): void
262
    {
263 13
        fwrite($this->shpFile, Util::packDouble($data['x']));
264 13
        fwrite($this->shpFile, Util::packDouble($data['y']));
265 13
        fwrite($this->shpFile, Util::packDouble($data['m']));
266
    }
267
268
    /** @param mixed[] $data */
269 13
    private function savePointZ(array $data): void
270
    {
271 13
        fwrite($this->shpFile, Util::packDouble($data['x']));
272 13
        fwrite($this->shpFile, Util::packDouble($data['y']));
273 13
        fwrite($this->shpFile, Util::packDouble($data['z']));
274 13
        fwrite($this->shpFile, Util::packDouble($data['m']));
275
    }
276
277
    private function loadNullRecord(): void
278
    {
279
        $this->shpData = [];
280
    }
281
282 25
    private function loadPointRecord(): void
283
    {
284 25
        $this->shpData = $this->loadPoint();
285
    }
286
287 13
    private function loadPointMRecord(): void
288
    {
289 13
        $this->shpData = $this->loadPointM();
290
    }
291
292 13
    private function loadPointZRecord(): void
293
    {
294 13
        $this->shpData = $this->loadPointZ();
295
    }
296
297 13
    private function savePointRecord(): void
298
    {
299 13
        $this->savePoint($this->shpData);
300
    }
301
302 13
    private function savePointMRecord(): void
303
    {
304 13
        $this->savePointM($this->shpData);
305
    }
306
307 13
    private function savePointZRecord(): void
308
    {
309 13
        $this->savePointZ($this->shpData);
310
    }
311
312 61
    private function loadBBox(): void
313
    {
314 61
        $this->shpData['xmin'] = $this->loadData('d', 8);
315 61
        $this->shpData['ymin'] = $this->loadData('d', 8);
316 61
        $this->shpData['xmax'] = $this->loadData('d', 8);
317 61
        $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 45
    private function loadPolyLineRecord(): void
404
    {
405 45
        $this->shpData = [];
406 45
        $this->loadBBox();
407
408 45
        $this->shpData['numparts'] = $this->loadData('V', 4);
409 45
        $this->shpData['numpoints'] = $this->loadData('V', 4);
410
411 45
        $numparts = $this->shpData['numparts'];
412 45
        $numpoints = $this->shpData['numpoints'];
413
414 45
        for ($i = 0; $i < $numparts; ++$i) {
415 45
            $this->shpData['parts'][$i] = $this->loadData('V', 4);
416
        }
417
418 45
        $part = 0;
419 45
        for ($i = 0; $i < $numpoints; ++$i) {
420 45
            if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) {
421 41
                ++$part;
422
            }
423
424
            if (
425 45
                ! isset($this->shpData['parts'][$part]['points'])
426 45
                || ! is_array($this->shpData['parts'][$part]['points'])
427
            ) {
428 45
                $this->shpData['parts'][$part] = ['points' => []];
429
            }
430
431 45
            $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 33
    private function savePolyLineRecord(): void
474
    {
475 33
        fwrite($this->shpFile, pack(
476 33
            'dddd',
477 33
            $this->shpData['xmin'],
478 33
            $this->shpData['ymin'],
479 33
            $this->shpData['xmax'],
480 33
            $this->shpData['ymax'],
481 33
        ));
482
483 33
        fwrite($this->shpFile, pack('VV', $this->shpData['numparts'], $this->shpData['numpoints']));
484
485 33
        $partIndex = 0;
486 33
        for ($i = 0; $i < $this->shpData['numparts']; ++$i) {
487 33
            fwrite($this->shpFile, pack('V', $partIndex));
488 33
            $partIndex += count($this->shpData['parts'][$i]['points']);
489
        }
490
491 33
        foreach ($this->shpData['parts'] as $partData) {
492 33
            foreach ($partData['points'] as $pointData) {
493 33
                $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 57
    private function adjustBBox(array $point): void
556
    {
557
        // Adjusts bounding box based on point
558 57
        foreach (['x', 'y', 'z', 'm'] as $direction) {
559 57
            if (! isset($point[$direction])) {
560 41
                continue;
561
            }
562
563 57
            $min = $direction . 'min';
564 57
            $max = $direction . 'max';
565 57
            if (! isset($this->shpData[$min]) || ($this->shpData[$min] > $point[$direction])) {
566 57
                $this->shpData[$min] = $point[$direction];
567
            }
568
569 57
            if (isset($this->shpData[$max]) && ($this->shpData[$max] >= $point[$direction])) {
570 45
                continue;
571
            }
572
573 57
            $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 57
    private function adjustPoint(array $point): array
586
    {
587 57
        if (in_array($this->shapeType, ShapeType::MEASURED_TYPES, true)) {
588 41
            $point['m'] ??= 0.0;
589
        }
590
591 57
        if (in_array($this->shapeType, ShapeType::TYPES_WITH_Z, true)) {
592 25
            $point['z'] ??= 0.0;
593
        }
594
595 57
        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 57
    public function addPoint(array $point, int $partIndex = 0): void
605
    {
606 57
        $point = $this->adjustPoint($point);
607 57
        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 21
                $this->shpData = $point;
617 21
                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 33
                $this->shpData['parts'][$partIndex]['points'][] = $point;
626 33
                $this->shpData['numparts'] = count($this->shpData['parts']);
627 33
                $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0);
628 33
                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 57
        $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 57
    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 57
        switch ($this->shapeType) {
727
            case ShapeType::NULL:
728
                $result = 0;
729
                break;
730
            case ShapeType::POINT:
731 13
                $result = 10;
732 13
                break;
733
            case ShapeType::POINT_M:
734 13
                $result = 10 + 4;
735 13
                break;
736
            case ShapeType::POINT_Z:
737 13
                $result = 10 + 8;
738 13
                break;
739
            case ShapeType::POLY_LINE:
740
            case ShapeType::POLYGON:
741 17
                $count = count($this->shpData['parts']);
742 17
                $result = 22 + 2 * $count;
743 17
                for ($i = 0; $i < $count; ++$i) {
744 17
                    $result += 8 * count($this->shpData['parts'][$i]['points']);
745
                }
746
747 17
                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 57
        return $result;
782
    }
783
784
    /** @param resource $dbfFile Opened DBF file */
785 66
    private function loadDBFData($dbfFile): void
786
    {
787 66
        $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 66
        unset($this->dbfData['deleted']);
789
    }
790
791
    /** @param resource $dbfFile */
792 45
    private function saveDBFData($dbfFile): void
793
    {
794 45
        if ($this->dbfData === []) {
795
            return;
796
        }
797
798 45
        unset($this->dbfData['deleted']);
799 45
        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 45
        } 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