Passed
Push — master ( 2377b8...7d8bb8 )
by Maurício
04:21 queued 15s
created

ShapeFile::setAllowNoDbf()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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 chr;
29
use function count;
30
use function dbase_close;
0 ignored issues
show
introduced by
The function dbase_close was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
31
use function dbase_create;
0 ignored issues
show
introduced by
The function dbase_create was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
32
use function dbase_delete_record;
0 ignored issues
show
introduced by
The function dbase_delete_record was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
33
use function dbase_open;
0 ignored issues
show
introduced by
The function dbase_open was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
34
use function dbase_pack;
0 ignored issues
show
introduced by
The function dbase_pack was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
35
use function extension_loaded;
36
use function fclose;
37
use function feof;
38
use function file_exists;
39
use function fopen;
40
use function fread;
41
use function fwrite;
42
use function in_array;
43
use function is_readable;
44
use function ord;
45
use function pack;
46
use function sprintf;
47
use function str_replace;
48
use function strpos;
49
use function strtoupper;
50
use function substr;
51
use function trim;
52
use function unlink;
53
54
/**
55
 * ShapeFile class.
56
 */
57
class ShapeFile
58
{
59
    public final const MAGIC = 0x270a;
60
61
    /** @var resource|false */
62
    private $shpFile = false;
63
64
    /** @var resource|false */
65
    private $shxFile = false;
66
67
    /** @var resource|false */
68
    private $dbfFile = false;
69
70
    /** @var mixed[]|null */
71
    private array|null $dbfHeader = null;
72
73
    public string $lastError = '';
74
75
    /**
76
     * The value for file length is the total length of the file in 16-bit words
77
     * (including the fifty 16-bit words that make up the header).
78
     */
79
    private int $fileLength = 50;
80
81
    /** @var array<int, ShapeRecord> */
82
    public array $records = [];
83
84
    private bool $allowNoDbf = false;
85
86
    /**
87
     * Checks whether dbase manipulations are supported.
88
     */
89 106
    public static function supportsDbase(): bool
90
    {
91 106
        return extension_loaded('dbase');
92
    }
93
94
    /**
95
     * @param ShapeType   $shapeType   File shape type, should be same as all records
96
     * @param mixed[]     $boundingBox File bounding box
97
     * @param string|null $fileName    File name
98
     */
99 122
    public function __construct(
100
        public ShapeType $shapeType,
101
        public array $boundingBox = [
102
            'xmin' => 0.0,
103
            'ymin' => 0.0,
104
            'xmax' => 0.0,
105
            'ymax' => 0.0,
106
        ],
107
        public string|null $fileName = null,
108
    ) {
109 122
    }
110
111 3
    public function setAllowNoDbf(bool $allowNoDbf): void
112
    {
113 3
        $this->allowNoDbf = $allowNoDbf;
114
    }
115
116
    /**
117
     * Loads shapefile and dbase (if supported).
118
     *
119
     * @param string $fileName File mask to load (eg. example.*)
120
     */
121 110
    public function loadFromFile(string $fileName): bool
122
    {
123 110
        if ($fileName !== '') {
124 106
            $this->fileName = $fileName;
125 106
            $result = $this->openSHPFile();
126
        } else {
127
            /* We operate on buffer emulated by readSHP / eofSHP */
128 4
            $result = true;
129
        }
130
131 110
        if ($result && ($this->openDBFFile())) {
132 90
            if (! $this->loadHeaders()) {
133 2
                $this->closeSHPFile();
134 2
                $this->closeDBFFile();
135
136 2
                return false;
137
            }
138
139 88
            if (! $this->loadRecords()) {
140
                $this->closeSHPFile();
141
                $this->closeDBFFile();
142
143
                return false;
144
            }
145
146 88
            $this->closeSHPFile();
147 88
            $this->closeDBFFile();
148
149 88
            return true;
150
        }
151
152 20
        return false;
153
    }
154
155
    /**
156
     * Saves shapefile.
157
     *
158
     * @param string|null $fileName Name of file, otherwise existing is used
159
     */
160 61
    public function saveToFile(string|null $fileName = null): bool
161
    {
162 61
        if ($fileName !== null) {
163 61
            $this->fileName = $fileName;
164
        }
165
166 61
        if (! $this->openSHPFile(true) || (! $this->openSHXFile(true)) || (! $this->createDBFFile())) {
167
            return false;
168
        }
169
170 61
        $this->saveHeaders();
171 61
        $this->saveRecords();
172 61
        $this->closeSHPFile();
173 61
        $this->closeSHXFile();
174 61
        $this->closeDBFFile();
175
176 61
        return true;
177
    }
178
179
    /**
180
     * Generates filename with given extension.
181
     *
182
     * @param string $extension Extension to use (including dot)
183
     */
184 113
    private function getFilename(string $extension): string
185
    {
186 113
        return str_replace('.*', $extension, (string) $this->fileName);
187
    }
188
189
    /**
190
     * Updates bounding box based on shpData.
191
     *
192
     * @param string  $type Type of box
193
     * @param mixed[] $data ShapeRecord shpData
194
     */
195 57
    private function updateBBox(string $type, array $data): void
196
    {
197 57
        $min = $type . 'min';
198 57
        $max = $type . 'max';
199
200
        if (
201 57
            ! isset($this->boundingBox[$min])
202 57
            || $this->boundingBox[$min] == 0.0 // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators
203 57
            || ($this->boundingBox[$min] > $data[$min])
204
        ) {
205 57
            $this->boundingBox[$min] = $data[$min];
206
        }
207
208
        if (
209 57
            isset($this->boundingBox[$max])
210 57
            && $this->boundingBox[$max] != 0.0 // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators
211 57
            && ($this->boundingBox[$max] >= $data[$max])
212
        ) {
213 9
            return;
214
        }
215
216 57
        $this->boundingBox[$max] = $data[$max];
217
    }
218
219
    /**
220
     * Adds record to shape file.
221
     *
222
     * @return int Number of added record
223
     */
224 57
    public function addRecord(ShapeRecord $record): int
225
    {
226 57
        if ($this->dbfHeader !== null) {
227 51
            $record->updateDBFInfo($this->dbfHeader);
228
        }
229
230 57
        $this->fileLength += $record->getContentLength() + 4;
231 57
        $this->records[] = $record;
232 57
        $this->records[count($this->records) - 1]->recordNumber = count($this->records);
233
234 57
        $this->updateBBox('x', $record->shpData);
235 57
        $this->updateBBox('y', $record->shpData);
236
237 57
        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...
238 32
            $this->updateBBox('m', $record->shpData);
239
        }
240
241 57
        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...
242 16
            $this->updateBBox('z', $record->shpData);
243
        }
244
245 57
        return count($this->records) - 1;
246
    }
247
248
    /**
249
     * Deletes record from shapefile.
250
     */
251 3
    public function deleteRecord(int $index): void
252
    {
253 3
        if (! isset($this->records[$index])) {
254
            return;
255
        }
256
257 3
        $this->fileLength -= $this->records[$index]->getContentLength() + 4;
258 3
        $count = count($this->records) - 1;
259 3
        for ($i = $index; $i < $count; ++$i) {
260 3
            $this->records[$i] = $this->records[$i + 1];
261
        }
262
263 3
        unset($this->records[count($this->records) - 1]);
264 3
        $this->deleteRecordFromDBF($index);
265
    }
266
267
    /**
268
     * Returns array defining fields in DBF file.
269
     *
270
     * @return mixed[]|null see setDBFHeader for more information
271
     */
272 4
    public function getDBFHeader(): array|null
273
    {
274 4
        return $this->dbfHeader;
275
    }
276
277
    /**
278
     * Changes array defining fields in DBF file, used in dbase_create call.
279
     *
280
     * @param mixed[] $header An array of arrays, each array describing the
281
     *                        format of one field of the database. Each
282
     *                        field consists of a name, a character indicating
283
     *                        the field type, and optionally, a length,
284
     *                        a precision and a nullable flag.
285
     */
286 57
    public function setDBFHeader(array $header): void
287
    {
288 57
        $this->dbfHeader = $header;
289
290 57
        foreach ($this->records as $record) {
291 9
            $record->updateDBFInfo($header);
292
        }
293
    }
294
295
    /**
296
     * Lookups value in the DBF file and returns index.
297
     */
298 4
    public function getIndexFromDBFData(string $field, string $value): int
299
    {
300 4
        foreach ($this->records as $index => $record) {
301
            if (
302 4
                isset($record->dbfData[$field]) &&
303 4
                (trim(strtoupper($record->dbfData[$field])) === strtoupper($value))
304
            ) {
305 3
                return $index;
306
            }
307
        }
308
309 4
        return -1;
310
    }
311
312
    /**
313
     * Loads DBF metadata.
314
     *
315
     * @return array{string, string, int, int}[]
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{string, string, int, int}[] at position 2 could not be parsed: Expected ':' at position 2, but found 'string'.
Loading history...
316
     */
317 88
    private function loadDBFHeader(): array
318
    {
319 88
        if (! self::supportsDbase()) {
320 19
            return [];
321
        }
322
323 69
        $dbfName = $this->getFilename('.dbf');
324 69
        if (! file_exists($dbfName)) {
325 3
            return [];
326
        }
327
328 66
        $dbfFile = fopen($dbfName, 'r');
329 66
        if ($dbfFile === false) {
330
            return [];
331
        }
332
333 66
        $result = [];
334 66
        $i = 1;
335
336 66
        while (true) {
337 66
            if (feof($dbfFile)) {
338
                break;
339
            }
340
341 66
            $buff32 = fread($dbfFile, 32);
342 66
            if ($i > 1) {
343 66
                if (substr($buff32, 0, 1) === chr(13)) {
344 66
                    break;
345
                }
346
347 66
                $pos = strpos(substr($buff32, 0, 10), chr(0));
348 66
                $pos = ($pos === false ? 10 : $pos);
349
350 66
                $fieldName = substr($buff32, 0, $pos);
351 66
                $fieldType = substr($buff32, 11, 1);
352 66
                $fieldLen = ord(substr($buff32, 16, 1));
353 66
                $fieldDec = ord(substr($buff32, 17, 1));
354
355 66
                $result[] = [$fieldName, $fieldType, $fieldLen, $fieldDec];
356
            }
357
358 66
            ++$i;
359
        }
360
361 66
        fclose($dbfFile);
362
363 66
        return $result;
364
    }
365
366
    /**
367
     * Deletes record from the DBF file.
368
     */
369 3
    private function deleteRecordFromDBF(int $index): void
370
    {
371 3
        if ($this->dbfFile === false || ! @dbase_delete_record($this->dbfFile, $index)) {
0 ignored issues
show
Bug introduced by
The function dbase_delete_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

371
        if ($this->dbfFile === false || ! @/** @scrutinizer ignore-call */ dbase_delete_record($this->dbfFile, $index)) {
Loading history...
372 3
            return;
373
        }
374
375
        dbase_pack($this->dbfFile);
0 ignored issues
show
Bug introduced by
The function dbase_pack 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

375
        /** @scrutinizer ignore-call */ 
376
        dbase_pack($this->dbfFile);
Loading history...
376
    }
377
378
    /**
379
     * Loads SHP file metadata.
380
     */
381 90
    private function loadHeaders(): bool
382
    {
383 90
        if (Util::loadData('N', $this->readSHP(4)) !== self::MAGIC) {
384 2
            $this->setError('Not a SHP file (file code mismatch)');
385
386 2
            return false;
387
        }
388
389
        /* Skip 20 unused bytes */
390 88
        $this->readSHP(20);
391
392 88
        $this->fileLength = (int) Util::loadData('N', $this->readSHP(4));
393
394
        /* We currently ignore version */
395 88
        $this->readSHP(4);
396
397 88
        $shapeType = Util::loadData('V', $this->readSHP(4));
398 88
        if ($shapeType === false) {
399
            $this->shapeType = ShapeType::Unknown;
400
        } else {
401 88
            $this->shapeType = ShapeType::tryFrom((int) $shapeType) ?? ShapeType::Unknown;
402
        }
403
404 88
        $this->boundingBox = [];
405 88
        $this->boundingBox['xmin'] = Util::loadData('d', $this->readSHP(8));
406 88
        $this->boundingBox['ymin'] = Util::loadData('d', $this->readSHP(8));
407 88
        $this->boundingBox['xmax'] = Util::loadData('d', $this->readSHP(8));
408 88
        $this->boundingBox['ymax'] = Util::loadData('d', $this->readSHP(8));
409 88
        $this->boundingBox['zmin'] = Util::loadData('d', $this->readSHP(8));
410 88
        $this->boundingBox['zmax'] = Util::loadData('d', $this->readSHP(8));
411 88
        $this->boundingBox['mmin'] = Util::loadData('d', $this->readSHP(8));
412 88
        $this->boundingBox['mmax'] = Util::loadData('d', $this->readSHP(8));
413
414 88
        $this->dbfHeader = $this->loadDBFHeader();
415
416 88
        return true;
417
    }
418
419
    /**
420
     * Saves bounding box record, possibly using 0 instead of not set values.
421
     *
422
     * @param resource $file File object
423
     * @param string   $type Bounding box dimension (eg. xmax, mmin...)
424
     */
425 61
    private function saveBBoxRecord($file, string $type): void
426
    {
427 61
        fwrite($file, Util::packDouble(
428 61
            $this->boundingBox[$type] ?? 0,
429 61
        ));
430
    }
431
432
    /**
433
     * Saves bounding box to a file.
434
     *
435
     * @param resource $file File object
436
     */
437 61
    private function saveBBox($file): void
438
    {
439 61
        $this->saveBBoxRecord($file, 'xmin');
440 61
        $this->saveBBoxRecord($file, 'ymin');
441 61
        $this->saveBBoxRecord($file, 'xmax');
442 61
        $this->saveBBoxRecord($file, 'ymax');
443 61
        $this->saveBBoxRecord($file, 'zmin');
444 61
        $this->saveBBoxRecord($file, 'zmax');
445 61
        $this->saveBBoxRecord($file, 'mmin');
446 61
        $this->saveBBoxRecord($file, 'mmax');
447
    }
448
449
    /**
450
     * Saves SHP and SHX file metadata.
451
     */
452 61
    private function saveHeaders(): void
453
    {
454 61
        if ($this->shpFile === false) {
455
            return;
456
        }
457
458 61
        fwrite($this->shpFile, pack('NNNNNN', self::MAGIC, 0, 0, 0, 0, 0));
0 ignored issues
show
Bug introduced by
It seems like $this->shpFile can also be of type true; however, parameter $stream of fwrite() does only seem to accept resource, 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

458
        fwrite(/** @scrutinizer ignore-type */ $this->shpFile, pack('NNNNNN', self::MAGIC, 0, 0, 0, 0, 0));
Loading history...
459 61
        fwrite($this->shpFile, pack('N', $this->fileLength));
460 61
        fwrite($this->shpFile, pack('V', 1000));
461 61
        fwrite($this->shpFile, pack('V', $this->shapeType->value));
462 61
        $this->saveBBox($this->shpFile);
0 ignored issues
show
Bug introduced by
It seems like $this->shpFile can also be of type true; however, parameter $file of PhpMyAdmin\ShapeFile\ShapeFile::saveBBox() does only seem to accept resource, 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

462
        $this->saveBBox(/** @scrutinizer ignore-type */ $this->shpFile);
Loading history...
463
464 61
        if ($this->shxFile === false) {
465
            return;
466
        }
467
468 61
        fwrite($this->shxFile, pack('NNNNNN', self::MAGIC, 0, 0, 0, 0, 0));
469 61
        fwrite($this->shxFile, pack('N', 50 + 4 * count($this->records)));
470 61
        fwrite($this->shxFile, pack('V', 1000));
471 61
        fwrite($this->shxFile, pack('V', $this->shapeType->value));
472 61
        $this->saveBBox($this->shxFile);
473
    }
474
475
    /**
476
     * Loads records from SHP file (and DBF).
477
     */
478 88
    private function loadRecords(): bool
479
    {
480
        /* Need to start at offset 100 */
481 88
        while (! $this->eofSHP()) {
482 88
            $record = new ShapeRecord(ShapeType::Unknown);
483 88
            $record->loadFromFile($this, $this->dbfFile);
484 88
            if ($record->lastError !== '') {
485
                $this->setError($record->lastError);
486
487
                return false;
488
            }
489
490 88
            if (($record->shapeType === ShapeType::Unknown) && $this->eofSHP()) {
491 88
                break;
492
            }
493
494 88
            $this->records[] = $record;
495
        }
496
497 88
        return true;
498
    }
499
500
    /**
501
     * Saves records to SHP and SHX files.
502
     */
503 61
    private function saveRecords(): void
504
    {
505 61
        $offset = 50;
506 61
        if ($this->records === [] || $this->shxFile === false || $this->shpFile === false) {
507 4
            return;
508
        }
509
510 57
        foreach ($this->records as $index => $record) {
511
            //Save the record to the .shp file
512 57
            $record->saveToFile($this->shpFile, $this->dbfFile, $index + 1);
513
514
            //Save the record to the .shx file
515 57
            fwrite($this->shxFile, pack('N', $offset));
0 ignored issues
show
Bug introduced by
It seems like $this->shxFile can also be of type true; however, parameter $stream of fwrite() does only seem to accept resource, 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

515
            fwrite(/** @scrutinizer ignore-type */ $this->shxFile, pack('N', $offset));
Loading history...
516 57
            fwrite($this->shxFile, pack('N', $record->getContentLength()));
517 57
            $offset += 4 + $record->getContentLength();
518
        }
519
    }
520
521
    /**
522
     * Generic interface to open files.
523
     *
524
     * @param bool   $toWrite   Whether file should be opened for writing
525
     * @param string $extension File extension
526
     * @param string $name      Verbose file name to report errors
527
     *
528
     * @return resource|false File handle
529
     */
530 110
    private function openFile(bool $toWrite, string $extension, string $name)
531
    {
532 110
        $shpName = $this->getFilename($extension);
533 110
        $result = @fopen($shpName, ($toWrite ? 'wb+' : 'rb'));
534 110
        if (! $result) {
0 ignored issues
show
introduced by
$result is of type false|resource, thus it always evaluated to false.
Loading history...
535 8
            $this->setError(sprintf('It wasn\'t possible to open the %s file "%s"', $name, $shpName));
536
537 8
            return false;
538
        }
539
540 102
        return $result;
541
    }
542
543
    /**
544
     * Opens SHP file.
545
     *
546
     * @param bool $toWrite Whether file should be opened for writing
547
     */
548 110
    private function openSHPFile(bool $toWrite = false): bool
549
    {
550 110
        $this->shpFile = $this->openFile($toWrite, '.shp', 'Shape');
551
552 110
        return (bool) $this->shpFile;
553
    }
554
555
    /**
556
     * Closes SHP file.
557
     */
558 94
    private function closeSHPFile(): void
559
    {
560 94
        if ($this->shpFile === false) {
561 1
            return;
562
        }
563
564 93
        fclose($this->shpFile);
0 ignored issues
show
Bug introduced by
It seems like $this->shpFile can also be of type true; however, parameter $stream of fclose() does only seem to accept resource, 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

564
        fclose(/** @scrutinizer ignore-type */ $this->shpFile);
Loading history...
565 93
        $this->shpFile = false;
566
    }
567
568
    /**
569
     * Opens SHX file.
570
     *
571
     * @param bool $toWrite Whether file should be opened for writing
572
     */
573 61
    private function openSHXFile(bool $toWrite = false): bool
574
    {
575 61
        $this->shxFile = $this->openFile($toWrite, '.shx', 'Index');
576
577 61
        return (bool) $this->shxFile;
578
    }
579
580
    /**
581
     * Closes SHX file.
582
     */
583 61
    private function closeSHXFile(): void
584
    {
585 61
        if ($this->shxFile === false) {
586
            return;
587
        }
588
589 61
        fclose($this->shxFile);
0 ignored issues
show
Bug introduced by
It seems like $this->shxFile can also be of type true; however, parameter $stream of fclose() does only seem to accept resource, 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

589
        fclose(/** @scrutinizer ignore-type */ $this->shxFile);
Loading history...
590 61
        $this->shxFile = false;
591
    }
592
593
    /**
594
     * Creates DBF file.
595
     */
596 61
    private function createDBFFile(): bool
597
    {
598 61
        if (! self::supportsDbase() || $this->dbfHeader === null || $this->dbfHeader === []) {
599 16
            $this->dbfFile = false;
600
601 16
            return true;
602
        }
603
604 45
        $dbfName = $this->getFilename('.dbf');
605
606
        /* Unlink existing file */
607 45
        if (file_exists($dbfName)) {
608 6
            unlink($dbfName);
609
        }
610
611
        /* Create new file */
612 45
        $this->dbfFile = @dbase_create($dbfName, $this->dbfHeader);
0 ignored issues
show
Bug introduced by
The function dbase_create 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

612
        $this->dbfFile = @/** @scrutinizer ignore-call */ dbase_create($dbfName, $this->dbfHeader);
Loading history...
613 45
        if ($this->dbfFile === false) {
614
            $this->setError(sprintf('It wasn\'t possible to create the DBase file "%s"', $dbfName));
615
616
            return false;
617
        }
618
619 45
        return true;
620
    }
621
622
    /**
623
     * Loads DBF file if supported.
624
     */
625 102
    private function openDBFFile(): bool
626
    {
627 102
        if (! self::supportsDbase()) {
628 21
            $this->dbfFile = false;
629
630 21
            return true;
631
        }
632
633 81
        $dbfName = $this->getFilename('.dbf');
634 81
        if (! is_readable($dbfName)) {
635 12
            if ($this->allowNoDbf) {
636 3
                return true;
637
            }
638
639 9
            $this->setError(sprintf('It wasn\'t possible to find the DBase file "%s"', $dbfName));
640
641 9
            return false;
642
        }
643
644 69
        $this->dbfFile = @dbase_open($dbfName, 0);
0 ignored issues
show
Bug introduced by
The function dbase_open 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

644
        $this->dbfFile = @/** @scrutinizer ignore-call */ dbase_open($dbfName, 0);
Loading history...
645 69
        if ($this->dbfFile === false) {
646 3
            $this->setError(sprintf('It wasn\'t possible to open the DBase file "%s"', $dbfName));
647
648 3
            return false;
649
        }
650
651 66
        return true;
652
    }
653
654
    /**
655
     * Closes DBF file.
656
     */
657 94
    private function closeDBFFile(): void
658
    {
659 94
        if ($this->dbfFile === false) {
660 28
            return;
661
        }
662
663 66
        dbase_close($this->dbfFile);
0 ignored issues
show
Bug introduced by
The function dbase_close 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

663
        /** @scrutinizer ignore-call */ 
664
        dbase_close($this->dbfFile);
Loading history...
664 66
        $this->dbfFile = false;
665
    }
666
667
    /**
668
     * Sets error message.
669
     */
670 22
    public function setError(string $error): void
671
    {
672 22
        $this->lastError = $error;
673
    }
674
675
    /**
676
     * Reads given number of bytes from SHP file.
677
     *
678
     * @param int<0, max> $bytes
679
     */
680 90
    public function readSHP(int $bytes): string|false
681
    {
682 90
        if ($this->shpFile === false) {
683 1
            return false;
684
        }
685
686 89
        return fread($this->shpFile, $bytes);
0 ignored issues
show
Bug introduced by
It seems like $this->shpFile can also be of type true; however, parameter $stream of fread() does only seem to accept resource, 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

686
        return fread(/** @scrutinizer ignore-type */ $this->shpFile, $bytes);
Loading history...
687
    }
688
689
    /**
690
     * Checks whether file is at EOF.
691
     */
692 88
    public function eofSHP(): bool
693
    {
694 88
        return feof($this->shpFile);
0 ignored issues
show
Bug introduced by
It seems like $this->shpFile can also be of type boolean; however, parameter $stream of feof() does only seem to accept resource, 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

694
        return feof(/** @scrutinizer ignore-type */ $this->shpFile);
Loading history...
695
    }
696
697
    /**
698
     * Returns shape name.
699
     *
700
     * @psalm-return non-empty-string
701
     */
702 4
    public function getShapeName(): string
703
    {
704 4
        return ShapeType::name($this->shapeType);
705
    }
706
707
    /**
708
     * Check whether file contains measure data.
709
     *
710
     * For some reason this is distinguished by zero bounding box in the
711
     * specification.
712
     */
713 32
    public function hasMeasure(): bool
714
    {
715
        // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator
716 32
        return $this->boundingBox['mmin'] != 0 || $this->boundingBox['mmax'] != 0;
717
    }
718
}
719