Passed
Push — master ( 228075...1455af )
by Maurício
13:04 queued 05:18
created

ShapeRecord::saveHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 1
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 sprintf;
35
use function strlen;
36
37
/**
38
 * ShapeFile record class.
39
 */
40
class ShapeRecord
41
{
42
    /** @var resource */
43
    private $shpFile = null;
44
    /** @var resource */
45
    private $dbfFile = null;
46
    /** @var ShapeFile */
47
    private $shapeFile = null;
48
49
    /** @var int */
50
    private $size = 0;
51
    /** @var int */
52
    private $read = 0;
53
54
    /** @var int|null */
55
    public $recordNumber = null;
56
57
    /** @var int */
58
    public $shapeType = null;
59
60
     /** @var string */
61
    public $lastError = '';
62
63
    /** @var array */
64
    public $shpData = [];
65
    /** @var array */
66
    public $dbfData = [];
67
68 175
    public function __construct(int $shapeType)
69
    {
70 175
        $this->shapeType = $shapeType;
71
    }
72
73
    /**
74
     * Loads record from files.
75
     *
76
     * @param ShapeFile $shapeFile The ShapeFile object
77
     * @param resource  $shpFile   Opened SHP file (by reference)
78
     * @param resource  $dbfFile   Opened DBF file (by reference)
79
     */
80 167
    public function loadFromFile(ShapeFile &$shapeFile, &$shpFile, &$dbfFile): void
81
    {
82 167
        $this->shapeFile = $shapeFile;
83 167
        $this->shpFile = $shpFile;
84 167
        $this->dbfFile = $dbfFile;
85 167
        $this->loadHeaders();
86
87
        /* No header read */
88 167
        if ($this->read === 0) {
89 167
            return;
90
        }
91
92 167
        switch ($this->shapeType) {
93 167
            case 0:
94
                $this->loadNullRecord();
95
                break;
96 167
            case 1:
97 47
                $this->loadPointRecord();
98 47
                break;
99 135
            case 21:
100 23
                $this->loadPointMRecord();
101 23
                break;
102 127
            case 11:
103 23
                $this->loadPointZRecord();
104 23
                break;
105 119
            case 3:
106 23
                $this->loadPolyLineRecord();
107 23
                break;
108 96
            case 23:
109 8
                $this->loadPolyLineMRecord();
110 8
                break;
111 88
            case 13:
112 8
                $this->loadPolyLineZRecord();
113 8
                break;
114 80
            case 5:
115 24
                $this->loadPolygonRecord();
116 24
                break;
117 56
            case 25:
118 8
                $this->loadPolygonMRecord();
119 8
                break;
120 48
            case 15:
121 16
                $this->loadPolygonZRecord();
122 16
                break;
123 32
            case 8:
124 8
                $this->loadMultiPointRecord();
125 8
                break;
126 24
            case 28:
127 8
                $this->loadMultiPointMRecord();
128 8
                break;
129 16
            case 18:
130 16
                $this->loadMultiPointZRecord();
131 16
                break;
132
            default:
133
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
0 ignored issues
show
Bug introduced by
It seems like $this->shapeType can also be of type false; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

133
                $this->setError(sprintf('The Shape Type "%s" is not supported.', /** @scrutinizer ignore-type */ $this->shapeType));
Loading history...
134
                break;
135
        }
136
137
        /* We need to skip rest of the record */
138 167
        while ($this->read < $this->size) {
139 48
            $this->loadData('V', 4);
140
        }
141
142
        /* Check if we didn't read too much */
143 167
        if ($this->read !== $this->size) {
144
            $this->setError(sprintf('Failed to parse record, read=%d, size=%d', $this->read, $this->size));
145
        }
146
147 167
        if (! ShapeFile::supportsDbase() || ! isset($this->dbfFile)) {
148 57
            return;
149
        }
150
151 110
        $this->loadDBFData();
152
    }
153
154
    /**
155
     * Saves record to files.
156
     *
157
     * @param resource $shpFile      Opened SHP file
158
     * @param resource $dbfFile      Opened DBF file
159
     * @param int      $recordNumber Record number
160
     */
161 111
    public function saveToFile(&$shpFile, &$dbfFile, int $recordNumber): void
162
    {
163 111
        $this->shpFile = $shpFile;
164 111
        $this->dbfFile = $dbfFile;
165 111
        $this->recordNumber = $recordNumber;
166 111
        $this->saveHeaders();
167
168 111
        switch ($this->shapeType) {
169 111
            case 0:
170
                // Nothing to save
171
                break;
172 111
            case 1:
173 23
                $this->savePointRecord();
174 23
                break;
175 103
            case 21:
176 23
                $this->savePointMRecord();
177 23
                break;
178 95
            case 11:
179 23
                $this->savePointZRecord();
180 23
                break;
181 87
            case 3:
182 23
                $this->savePolyLineRecord();
183 23
                break;
184 64
            case 23:
185 8
                $this->savePolyLineMRecord();
186 8
                break;
187 56
            case 13:
188 8
                $this->savePolyLineZRecord();
189 8
                break;
190 48
            case 5:
191 8
                $this->savePolygonRecord();
192 8
                break;
193 40
            case 25:
194 8
                $this->savePolygonMRecord();
195 8
                break;
196 32
            case 15:
197 8
                $this->savePolygonZRecord();
198 8
                break;
199 24
            case 8:
200 8
                $this->saveMultiPointRecord();
201 8
                break;
202 16
            case 28:
203 8
                $this->saveMultiPointMRecord();
204 8
                break;
205 8
            case 18:
206 8
                $this->saveMultiPointZRecord();
207 8
                break;
208
            default:
209
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
210
                break;
211
        }
212
213 111
        if (! ShapeFile::supportsDbase() || $this->dbfFile === null) {
214 36
            return;
215
        }
216
217 75
        $this->saveDBFData();
218
    }
219
220
    /**
221
     * Updates DBF data to match header.
222
     *
223
     * @param array $header DBF structure header
224
     */
225 111
    public function updateDBFInfo(array $header): void
226
    {
227 111
        $tmp = $this->dbfData;
228 111
        unset($this->dbfData);
229 111
        $this->dbfData = [];
230 111
        foreach ($header as $value) {
231 111
            $this->dbfData[$value[0]] = $tmp[$value[0]] ?? '';
232
        }
233
    }
234
235
    /**
236
     * Reads data.
237
     *
238
     * @param string $type  type for unpack()
239
     * @param int    $count number of bytes
240
     *
241
     * @return mixed
242
     */
243 167
    private function loadData(string $type, int $count)
244
    {
245 167
        $data = $this->shapeFile->readSHP($count);
246 167
        if ($data === false) {
247
            return false;
248
        }
249
250 167
        $this->read += strlen($data);
251
252 167
        return Util::loadData($type, $data);
253
    }
254
255
    /**
256
     * Loads metadata header from a file.
257
     */
258 167
    private function loadHeaders(): void
259
    {
260 167
        $this->shapeType = false;
0 ignored issues
show
Documentation Bug introduced by
The property $shapeType was declared of type integer, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
261 167
        $this->recordNumber = $this->loadData('N', 4);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->loadData('N', 4) can also be of type false. However, the property $recordNumber is declared as type integer|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
262 167
        if ($this->recordNumber === false) {
263 167
            return;
264
        }
265
266
        // We read the length of the record
267 167
        $this->size = $this->loadData('N', 4);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->loadData('N', 4) can also be of type false. However, the property $size is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
268 167
        if ($this->size === false) {
269
            return;
270
        }
271
272 167
        $this->size = ($this->size * 2) + 8;
273 167
        $this->shapeType = $this->loadData('V', 4);
274
    }
275
276
    /**
277
     * Saves metadata header to a file.
278
     */
279 111
    private function saveHeaders(): void
280
    {
281 111
        fwrite($this->shpFile, pack('N', $this->recordNumber));
282 111
        fwrite($this->shpFile, pack('N', $this->getContentLength()));
283 111
        fwrite($this->shpFile, pack('V', $this->shapeType));
284
    }
285
286 167
    private function loadPoint(): array
287
    {
288 167
        $data = [];
289
290 167
        $data['x'] = $this->loadData('d', 8);
291 167
        $data['y'] = $this->loadData('d', 8);
292
293 167
        return $data;
294
    }
295
296 23
    private function loadPointM(): array
297
    {
298 23
        $data = $this->loadPoint();
299
300 23
        $data['m'] = $this->loadData('d', 8);
301
302 23
        return $data;
303
    }
304
305 23
    private function loadPointZ(): array
306
    {
307 23
        $data = $this->loadPoint();
308
309 23
        $data['z'] = $this->loadData('d', 8);
310 23
        $data['m'] = $this->loadData('d', 8);
311
312 23
        return $data;
313
    }
314
315 95
    private function savePoint(array $data): void
316
    {
317 95
        fwrite($this->shpFile, Util::packDouble($data['x']));
318 95
        fwrite($this->shpFile, Util::packDouble($data['y']));
319
    }
320
321 23
    private function savePointM(array $data): void
322
    {
323 23
        fwrite($this->shpFile, Util::packDouble($data['x']));
324 23
        fwrite($this->shpFile, Util::packDouble($data['y']));
325 23
        fwrite($this->shpFile, Util::packDouble($data['m']));
326
    }
327
328 23
    private function savePointZ(array $data): void
329
    {
330 23
        fwrite($this->shpFile, Util::packDouble($data['x']));
331 23
        fwrite($this->shpFile, Util::packDouble($data['y']));
332 23
        fwrite($this->shpFile, Util::packDouble($data['z']));
333 23
        fwrite($this->shpFile, Util::packDouble($data['m']));
334
    }
335
336
    private function loadNullRecord(): void
337
    {
338
        $this->shpData = [];
339
    }
340
341 47
    private function loadPointRecord(): void
342
    {
343 47
        $this->shpData = $this->loadPoint();
344
    }
345
346 23
    private function loadPointMRecord(): void
347
    {
348 23
        $this->shpData = $this->loadPointM();
349
    }
350
351 23
    private function loadPointZRecord(): void
352
    {
353 23
        $this->shpData = $this->loadPointZ();
354
    }
355
356 23
    private function savePointRecord(): void
357
    {
358 23
        $this->savePoint($this->shpData);
359
    }
360
361 23
    private function savePointMRecord(): void
362
    {
363 23
        $this->savePointM($this->shpData);
364
    }
365
366 23
    private function savePointZRecord(): void
367
    {
368 23
        $this->savePointZ($this->shpData);
369
    }
370
371 119
    private function loadBBox(): void
372
    {
373 119
        $this->shpData['xmin'] = $this->loadData('d', 8);
374 119
        $this->shpData['ymin'] = $this->loadData('d', 8);
375 119
        $this->shpData['xmax'] = $this->loadData('d', 8);
376 119
        $this->shpData['ymax'] = $this->loadData('d', 8);
377
    }
378
379 32
    private function loadMultiPointRecord(): void
380
    {
381 32
        $this->shpData = [];
382 32
        $this->loadBBox();
383
384 32
        $this->shpData['numpoints'] = $this->loadData('V', 4);
385
386 32
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
387 32
            $this->shpData['points'][] = $this->loadPoint();
388
        }
389
    }
390
391 24
    private function loadMultiPointMZRecord(string $type): void
392
    {
393
        /* The m dimension is optional, depends on bounding box data */
394 24
        if ($type === 'm' && ! $this->shapeFile->hasMeasure()) {
395 24
            return;
396
        }
397
398 16
        $this->shpData[$type . 'min'] = $this->loadData('d', 8);
399 16
        $this->shpData[$type . 'max'] = $this->loadData('d', 8);
400
401 16
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
402 16
            $this->shpData['points'][$i][$type] = $this->loadData('d', 8);
403
        }
404
    }
405
406 8
    private function loadMultiPointMRecord(): void
407
    {
408 8
        $this->loadMultiPointRecord();
409
410 8
        $this->loadMultiPointMZRecord('m');
411
    }
412
413 16
    private function loadMultiPointZRecord(): void
414
    {
415 16
        $this->loadMultiPointRecord();
416
417 16
        $this->loadMultiPointMZRecord('z');
418 16
        $this->loadMultiPointMZRecord('m');
419
    }
420
421 24
    private function saveMultiPointRecord(): void
422
    {
423 24
        fwrite($this->shpFile, pack(
424
            'dddd',
425 24
            $this->shpData['xmin'],
426 24
            $this->shpData['ymin'],
427 24
            $this->shpData['xmax'],
428 24
            $this->shpData['ymax']
429
        ));
430
431 24
        fwrite($this->shpFile, pack('V', $this->shpData['numpoints']));
432
433 24
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
434 24
            $this->savePoint($this->shpData['points'][$i]);
435
        }
436
    }
437
438 16
    private function saveMultiPointMZRecord(string $type): void
439
    {
440 16
        fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
441
442 16
        for ($i = 0; $i < $this->shpData['numpoints']; ++$i) {
443 16
            fwrite($this->shpFile, Util::packDouble($this->shpData['points'][$i][$type]));
444
        }
445
    }
446
447 8
    private function saveMultiPointMRecord(): void
448
    {
449 8
        $this->saveMultiPointRecord();
450
451 8
        $this->saveMultiPointMZRecord('m');
452
    }
453
454 8
    private function saveMultiPointZRecord(): void
455
    {
456 8
        $this->saveMultiPointRecord();
457
458 8
        $this->saveMultiPointMZRecord('z');
459 8
        $this->saveMultiPointMZRecord('m');
460
    }
461
462 87
    private function loadPolyLineRecord(): void
463
    {
464 87
        $this->shpData = [];
465 87
        $this->loadBBox();
466
467 87
        $this->shpData['numparts'] = $this->loadData('V', 4);
468 87
        $this->shpData['numpoints'] = $this->loadData('V', 4);
469
470 87
        $numparts = $this->shpData['numparts'];
471 87
        $numpoints = $this->shpData['numpoints'];
472
473 87
        for ($i = 0; $i < $numparts; ++$i) {
474 87
            $this->shpData['parts'][$i] = $this->loadData('V', 4);
475
        }
476
477 87
        $part = 0;
478 87
        for ($i = 0; $i < $numpoints; ++$i) {
479 87
            if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) {
480 79
                ++$part;
481
            }
482
483
            if (
484 87
                ! isset($this->shpData['parts'][$part]['points'])
485 87
                || ! is_array($this->shpData['parts'][$part]['points'])
486
            ) {
487 87
                $this->shpData['parts'][$part] = ['points' => []];
488
            }
489
490 87
            $this->shpData['parts'][$part]['points'][] = $this->loadPoint();
491
        }
492
    }
493
494 40
    private function loadPolyLineMZRecord(string $type): void
495
    {
496
        /* The m dimension is optional, depends on bounding box data */
497 40
        if ($type === 'm' && ! $this->shapeFile->hasMeasure()) {
498 40
            return;
499
        }
500
501 24
        $this->shpData[$type . 'min'] = $this->loadData('d', 8);
502 24
        $this->shpData[$type . 'max'] = $this->loadData('d', 8);
503
504 24
        $numparts = $this->shpData['numparts'];
505 24
        $numpoints = $this->shpData['numpoints'];
506
507 24
        $part = 0;
508 24
        for ($i = 0; $i < $numpoints; ++$i) {
509 24
            if ($part + 1 < $numparts && $i === $this->shpData['parts'][$part + 1]) {
510
                ++$part;
511
            }
512
513 24
            $this->shpData['parts'][$part]['points'][$i][$type] = $this->loadData('d', 8);
514
        }
515
    }
516
517 16
    private function loadPolyLineMRecord(): void
518
    {
519 16
        $this->loadPolyLineRecord();
520
521 16
        $this->loadPolyLineMZRecord('m');
522
    }
523
524 24
    private function loadPolyLineZRecord(): void
525
    {
526 24
        $this->loadPolyLineRecord();
527
528 24
        $this->loadPolyLineMZRecord('z');
529 24
        $this->loadPolyLineMZRecord('m');
530
    }
531
532 63
    private function savePolyLineRecord(): void
533
    {
534 63
        fwrite($this->shpFile, pack(
535
            'dddd',
536 63
            $this->shpData['xmin'],
537 63
            $this->shpData['ymin'],
538 63
            $this->shpData['xmax'],
539 63
            $this->shpData['ymax']
540
        ));
541
542 63
        fwrite($this->shpFile, pack('VV', $this->shpData['numparts'], $this->shpData['numpoints']));
543
544 63
        $partIndex = 0;
545 63
        for ($i = 0; $i < $this->shpData['numparts']; ++$i) {
546 63
            fwrite($this->shpFile, pack('V', $partIndex));
547 63
            $partIndex += count($this->shpData['parts'][$i]['points']);
548
        }
549
550 63
        foreach ($this->shpData['parts'] as $partData) {
551 63
            foreach ($partData['points'] as $pointData) {
552 63
                $this->savePoint($pointData);
553
            }
554
        }
555
    }
556
557 32
    private function savePolyLineMZRecord(string $type): void
558
    {
559 32
        fwrite($this->shpFile, pack('dd', $this->shpData[$type . 'min'], $this->shpData[$type . 'max']));
560
561 32
        foreach ($this->shpData['parts'] as $partData) {
562 32
            foreach ($partData['points'] as $pointData) {
563 32
                fwrite($this->shpFile, Util::packDouble($pointData[$type]));
564
            }
565
        }
566
    }
567
568 16
    private function savePolyLineMRecord(): void
569
    {
570 16
        $this->savePolyLineRecord();
571
572 16
        $this->savePolyLineMZRecord('m');
573
    }
574
575 16
    private function savePolyLineZRecord(): void
576
    {
577 16
        $this->savePolyLineRecord();
578
579 16
        $this->savePolyLineMZRecord('z');
580 16
        $this->savePolyLineMZRecord('m');
581
    }
582
583 24
    private function loadPolygonRecord(): void
584
    {
585 24
        $this->loadPolyLineRecord();
586
    }
587
588 8
    private function loadPolygonMRecord(): void
589
    {
590 8
        $this->loadPolyLineMRecord();
591
    }
592
593 16
    private function loadPolygonZRecord(): void
594
    {
595 16
        $this->loadPolyLineZRecord();
596
    }
597
598 8
    private function savePolygonRecord(): void
599
    {
600 8
        $this->savePolyLineRecord();
601
    }
602
603 8
    private function savePolygonMRecord(): void
604
    {
605 8
        $this->savePolyLineMRecord();
606
    }
607
608 8
    private function savePolygonZRecord(): void
609
    {
610 8
        $this->savePolyLineZRecord();
611
    }
612
613 111
    private function adjustBBox(array $point): void
614
    {
615
        // Adjusts bounding box based on point
616 81
        $directions = [
617
            'x',
618
            'y',
619
            'z',
620
            'm',
621
        ];
622 111
        foreach ($directions as $direction) {
623 111
            if (! isset($point[$direction])) {
624 79
                continue;
625
            }
626
627 111
            $min = $direction . 'min';
628 111
            $max = $direction . 'max';
629 111
            if (! isset($this->shpData[$min]) || ($this->shpData[$min] > $point[$direction])) {
630 111
                $this->shpData[$min] = $point[$direction];
631
            }
632
633 111
            if (isset($this->shpData[$max]) && ($this->shpData[$max] >= $point[$direction])) {
634 87
                continue;
635
            }
636
637 111
            $this->shpData[$max] = $point[$direction];
638
        }
639
    }
640
641
    /**
642
     * Sets dimension to 0 if not set.
643
     *
644
     * @param array  $point     Point to check
645
     * @param string $dimension Dimension to check
646
     *
647
     * @return array
648
     */
649 79
    private function fixPoint(array $point, string $dimension): array
650
    {
651 79
        if (! isset($point[$dimension])) {
652 64
            $point[$dimension] = 0.0; // no_value
653
        }
654
655 79
        return $point;
656
    }
657
658
    /**
659
     * Adjust point and bounding box when adding point.
660
     *
661
     * @param array $point Point data
662
     *
663
     * @return array Fixed point data
664
     */
665 111
    private function adjustPoint(array $point): array
666
    {
667 111
        $type = $this->shapeType / 10;
668 111
        if ($type >= 2) {
669 47
            $point = $this->fixPoint($point, 'm');
670 79
        } elseif ($type >= 1) {
671 47
            $point = $this->fixPoint($point, 'z');
672 47
            $point = $this->fixPoint($point, 'm');
673
        }
674
675 111
        return $point;
676
    }
677
678
    /**
679
     * Adds point to a record.
680
     *
681
     * @param array $point     Point data
682
     * @param int   $partIndex Part index
683
     */
684 111
    public function addPoint(array $point, int $partIndex = 0): void
685
    {
686 111
        $point = $this->adjustPoint($point);
687 111
        switch ($this->shapeType) {
688 111
            case 0:
689
                //Don't add anything
690
                return;
691
692 111
            case 1:
693 103
            case 11:
694 95
            case 21:
695
                //Substitutes the value of the current point
696 39
                $this->shpData = $point;
697 39
                break;
698 87
            case 3:
699 64
            case 5:
700 56
            case 13:
701 48
            case 15:
702 40
            case 23:
703 32
            case 25:
704
                //Adds a new point to the selected part
705 63
                $this->shpData['parts'][$partIndex]['points'][] = $point;
706 63
                $this->shpData['numparts'] = count($this->shpData['parts']);
707 63
                $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0);
708 63
                break;
709 24
            case 8:
710 16
            case 18:
711 8
            case 28:
712
                //Adds a new point
713 24
                $this->shpData['points'][] = $point;
714 24
                $this->shpData['numpoints'] = 1 + ($this->shpData['numpoints'] ?? 0);
715 24
                break;
716
            default:
717
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
718
719
                return;
720
        }
721
722 111
        $this->adjustBBox($point);
723
    }
724
725
    /**
726
     * Deletes point from a record.
727
     *
728
     * @param int $pointIndex Point index
729
     * @param int $partIndex  Part index
730
     */
731 96
    public function deletePoint(int $pointIndex = 0, int $partIndex = 0): void
732
    {
733 96
        switch ($this->shapeType) {
734 96
            case 0:
735
                //Don't delete anything
736
                break;
737 96
            case 1:
738 88
            case 11:
739 80
            case 21:
740
                //Sets the value of the point to zero
741 24
                $this->shpData['x'] = 0.0;
742 24
                $this->shpData['y'] = 0.0;
743 24
                if (in_array($this->shapeType, [11, 21])) {
744 16
                    $this->shpData['m'] = 0.0;
745
                }
746
747 24
                if (in_array($this->shapeType, [11])) {
748 8
                    $this->shpData['z'] = 0.0;
749
                }
750
751 24
                break;
752 72
            case 3:
753 64
            case 5:
754 56
            case 13:
755 48
            case 15:
756 40
            case 23:
757 32
            case 25:
758
                //Deletes the point from the selected part, if exists
759
                if (
760 48
                    isset($this->shpData['parts'][$partIndex])
761 48
                    && isset($this->shpData['parts'][$partIndex]['points'][$pointIndex])
762
                ) {
763 48
                    $count = count($this->shpData['parts'][$partIndex]['points']) - 1;
764 48
                    for ($i = $pointIndex; $i < $count; ++$i) {
765 48
                        $point = $this->shpData['parts'][$partIndex]['points'][$i + 1];
766 48
                        $this->shpData['parts'][$partIndex]['points'][$i] = $point;
767
                    }
768
769 48
                    $count = count($this->shpData['parts'][$partIndex]['points']) - 1;
770 48
                    unset($this->shpData['parts'][$partIndex]['points'][$count]);
771
772 48
                    $this->shpData['numparts'] = count($this->shpData['parts']);
773 48
                    --$this->shpData['numpoints'];
774
                }
775
776 48
                break;
777 24
            case 8:
778 16
            case 18:
779 8
            case 28:
780
                //Deletes the point, if exists
781 24
                if (isset($this->shpData['points'][$pointIndex])) {
782 24
                    $count = count($this->shpData['points']) - 1;
783 24
                    for ($i = $pointIndex; $i < $count; ++$i) {
784 24
                        $this->shpData['points'][$i] = $this->shpData['points'][$i + 1];
785
                    }
786
787 24
                    unset($this->shpData['points'][count($this->shpData['points']) - 1]);
788
789 24
                    --$this->shpData['numpoints'];
790
                }
791
792 24
                break;
793
            default:
794
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
795
                break;
796
        }
797
    }
798
799
    /**
800
     * Returns length of content.
801
     */
802 111
    public function getContentLength(): ?int
803
    {
804
        // The content length for a record is the length of the record contents section measured in 16-bit words.
805
        // one coordinate makes 4 16-bit words (64 bit double)
806 111
        switch ($this->shapeType) {
807 111
            case 0:
808
                $result = 0;
809
                break;
810 111
            case 1:
811 23
                $result = 10;
812 23
                break;
813 103
            case 21:
814 23
                $result = 10 + 4;
815 23
                break;
816 95
            case 11:
817 23
                $result = 10 + 8;
818 23
                break;
819 87
            case 3:
820 64
            case 5:
821 31
                $count = count($this->shpData['parts']);
822 31
                $result = 22 + 2 * $count;
823 31
                for ($i = 0; $i < $count; ++$i) {
824 31
                    $result += 8 * count($this->shpData['parts'][$i]['points']);
825
                }
826
827 31
                break;
828 56
            case 23:
829 48
            case 25:
830 16
                $count = count($this->shpData['parts']);
831 16
                $result = 22 + (2 * 4) + 2 * $count;
832 16
                for ($i = 0; $i < $count; ++$i) {
833 16
                    $result += (8 + 4) * count($this->shpData['parts'][$i]['points']);
834
                }
835
836 16
                break;
837 40
            case 13:
838 32
            case 15:
839 16
                $count = count($this->shpData['parts']);
840 16
                $result = 22 + (4 * 4) + 2 * $count;
841 16
                for ($i = 0; $i < $count; ++$i) {
842 16
                    $result += (8 + 8) * count($this->shpData['parts'][$i]['points']);
843
                }
844
845 16
                break;
846 24
            case 8:
847 8
                $result = 20 + 8 * count($this->shpData['points']);
848 8
                break;
849 16
            case 28:
850 8
                $result = 20 + (2 * 4) + (8 + 4) * count($this->shpData['points']);
851 8
                break;
852 8
            case 18:
853 8
                $result = 20 + (4 * 4) + (8 + 8) * count($this->shpData['points']);
854 8
                break;
855
            default:
856
                $result = null;
857
                $this->setError(sprintf('The Shape Type "%s" is not supported.', $this->shapeType));
858
                break;
859
        }
860
861 111
        return $result;
862
    }
863
864 110
    private function loadDBFData(): void
865
    {
866 110
        $this->dbfData = @dbase_get_record_with_names($this->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

866
        $this->dbfData = @/** @scrutinizer ignore-call */ dbase_get_record_with_names($this->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...
867 110
        unset($this->dbfData['deleted']);
868
    }
869
870 75
    private function saveDBFData(): void
871
    {
872 75
        if (count($this->dbfData) === 0) {
873
            return;
874
        }
875
876 75
        unset($this->dbfData['deleted']);
877 75
        if ($this->recordNumber <= dbase_numrecords($this->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

877
        if ($this->recordNumber <= /** @scrutinizer ignore-call */ dbase_numrecords($this->dbfFile)) {
Loading history...
introduced by
Function dbase_numrecords() should not be referenced via a fallback global name, but via a use statement.
Loading history...
878
            if (! dbase_replace_record($this->dbfFile, array_values($this->dbfData), $this->recordNumber)) {
0 ignored issues
show
introduced by
Function dbase_replace_record() should not be referenced via a fallback global name, but via a use statement.
Loading history...
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

878
            if (! /** @scrutinizer ignore-call */ dbase_replace_record($this->dbfFile, array_values($this->dbfData), $this->recordNumber)) {
Loading history...
879
                $this->setError('I wasn\'t possible to update the information in the DBF file.');
880
            }
881
        } else {
882 75
            if (! dbase_add_record($this->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

882
            if (! /** @scrutinizer ignore-call */ dbase_add_record($this->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...
883
                $this->setError('I wasn\'t possible to add the information to the DBF file.');
884
            }
885
        }
886
    }
887
888
    /**
889
     * Sets error message.
890
     */
891
    public function setError(string $error): void
892
    {
893
        $this->lastError = $error;
894
    }
895
896
    /**
897
     * Returns shape name.
898
     */
899 8
    public function getShapeName(): string
900
    {
901 8
        return Util::nameShape($this->shapeType);
902
    }
903
}
904