ShapeFile   F
last analyzed

Complexity

Total Complexity 92

Size/Duplication

Total Lines 638
Duplicated Lines 0 %

Test Coverage

Coverage 93.8%

Importance

Changes 10
Bugs 0 Features 0
Metric Value
eloc 220
c 10
b 0
f 0
dl 0
loc 638
ccs 227
cts 242
cp 0.938
rs 2
wmc 92

32 Methods

Rating   Name   Duplication   Size   Complexity  
A loadFromFile() 0 32 6
B loadDBFHeader() 0 38 7
A saveToFile() 0 17 5
A getFilename() 0 3 1
A setError() 0 3 1
A eofSHP() 0 3 1
A getIndexFromDBFData() 0 12 4
A openSHPFile() 0 5 1
A saveBBox() 0 10 1
A openFile() 0 11 3
A closeDBFFile() 0 8 2
A closeSHPFile() 0 8 2
A saveBBoxRecord() 0 4 1
A setDBFHeader() 0 6 2
A openDBFFile() 0 23 4
B updateBBox() 0 22 7
A readSHP() 0 7 2
A saveHeaders() 0 21 3
A loadRecords() 0 20 5
A getDBFHeader() 0 3 1
A supportsDbase() 0 3 1
A deleteRecord() 0 14 3
A addRecord() 0 22 4
A __construct() 0 10 1
A saveRecords() 0 15 5
A deleteRecordFromDBF() 0 7 3
A openSHXFile() 0 5 1
A closeSHXFile() 0 8 2
A loadHeaders() 0 34 4
A createDBFFile() 0 24 6
A getShapeName() 0 3 1
A hasMeasure() 0 4 2

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * phpMyAdmin ShapeFile library
7
 * <https://github.com/phpmyadmin/shapefile/>.
8
 *
9
 * Copyright 2006-2007 Ovidio <ovidio AT users.sourceforge.net>
10
 * Copyright 2016 - 2017 Michal Čihař <[email protected]>
11
 *
12
 * This program is free software; you can redistribute it and/or
13
 * modify it under the terms of the GNU General Public License
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 * along with this program; if not, you can download one from
23
 * https://www.gnu.org/copyleft/gpl.html.
24
 */
25
26
namespace PhpMyAdmin\ShapeFile;
27
28
use function chr;
29
use function count;
30
use function extension_loaded;
31
use function fclose;
32
use function feof;
33
use function file_exists;
34
use function fopen;
35
use function fread;
36
use function fwrite;
37
use function in_array;
38
use function is_readable;
39
use function ord;
40
use function pack;
41
use function sprintf;
42
use function str_replace;
43
use function strpos;
44
use function strtoupper;
45
use function substr;
46
use function trim;
47
use function unlink;
48
49
/**
50
 * ShapeFile class.
51
 */
52
class ShapeFile
53
{
54
    public final const MAGIC = 0x270a;
55
56
    /** @var resource|false */
57
    private $shpFile = false;
58
59
    /** @var resource|false */
60
    private $shxFile = false;
61
62
    /** @var resource|false */
63
    private $dbfFile = false;
64
65
    /** @var mixed[]|null */
66
    private array|null $dbfHeader = null;
67
68
    public string $lastError = '';
69
70
    /**
71
     * The value for file length is the total length of the file in 16-bit words
72
     * (including the fifty 16-bit words that make up the header).
73
     */
74
    private int $fileLength = 50;
75
76
    /** @var array<int, ShapeRecord> */
77
    public array $records = [];
78
79
    /**
80
     * Checks whether dbase manipulations are supported.
81
     */
82 98
    public static function supportsDbase(): bool
83
    {
84 98
        return extension_loaded('dbase');
85
    }
86
87
    /**
88
     * @param int         $shapeType   File shape type, should be same as all records
89
     * @param mixed[]     $boundingBox File bounding box
90
     * @param string|null $fileName    File name
91
     */
92 114
    public function __construct(
93
        public int $shapeType,
94
        public array $boundingBox = [
95
            'xmin' => 0.0,
96
            'ymin' => 0.0,
97
            'xmax' => 0.0,
98
            'ymax' => 0.0,
99
        ],
100
        public string|null $fileName = null,
101
    ) {
102 114
    }
103
104
    /**
105
     * Loads shapefile and dbase (if supported).
106
     *
107
     * @param string $fileName File mask to load (eg. example.*)
108
     */
109 102
    public function loadFromFile(string $fileName): bool
110
    {
111 102
        if ($fileName !== '') {
112 98
            $this->fileName = $fileName;
113 98
            $result = $this->openSHPFile();
114
        } else {
115
            /* We operate on buffer emulated by readSHP / eofSHP */
116 4
            $result = true;
117
        }
118
119 102
        if ($result && ($this->openDBFFile())) {
120 86
            if (! $this->loadHeaders()) {
121 4
                $this->closeSHPFile();
122 4
                $this->closeDBFFile();
123
124 4
                return false;
125
            }
126
127 82
            if (! $this->loadRecords()) {
128
                $this->closeSHPFile();
129
                $this->closeDBFFile();
130
131
                return false;
132
            }
133
134 82
            $this->closeSHPFile();
135 82
            $this->closeDBFFile();
136
137 82
            return true;
138
        }
139
140 16
        return false;
141
    }
142
143
    /**
144
     * Saves shapefile.
145
     *
146
     * @param string|null $fileName Name of file, otherwise existing is used
147
     */
148 58
    public function saveToFile(string|null $fileName = null): bool
149
    {
150 58
        if ($fileName !== null) {
151 58
            $this->fileName = $fileName;
152
        }
153
154 58
        if (! $this->openSHPFile(true) || (! $this->openSHXFile(true)) || (! $this->createDBFFile())) {
155
            return false;
156
        }
157
158 58
        $this->saveHeaders();
159 58
        $this->saveRecords();
160 58
        $this->closeSHPFile();
161 58
        $this->closeSHXFile();
162 58
        $this->closeDBFFile();
163
164 58
        return true;
165
    }
166
167
    /**
168
     * Generates filename with given extension.
169
     *
170
     * @param string $extension Extension to use (including dot)
171
     */
172 104
    private function getFilename(string $extension): string
173
    {
174 104
        return str_replace('.*', $extension, (string) $this->fileName);
175
    }
176
177
    /**
178
     * Updates bounding box based on shpData.
179
     *
180
     * @param string  $type Type of box
181
     * @param mixed[] $data ShapeRecord shpData
182
     */
183 54
    private function updateBBox(string $type, array $data): void
184
    {
185 54
        $min = $type . 'min';
186 54
        $max = $type . 'max';
187
188
        if (
189 54
            ! isset($this->boundingBox[$min])
190 54
            || $this->boundingBox[$min] == 0.0 // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators
191 54
            || ($this->boundingBox[$min] > $data[$min])
192
        ) {
193 54
            $this->boundingBox[$min] = $data[$min];
194
        }
195
196
        if (
197 54
            isset($this->boundingBox[$max])
198 54
            && $this->boundingBox[$max] != 0.0 // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators
199 54
            && ($this->boundingBox[$max] >= $data[$max])
200
        ) {
201 6
            return;
202
        }
203
204 54
        $this->boundingBox[$max] = $data[$max];
205
    }
206
207
    /**
208
     * Adds record to shape file.
209
     *
210
     * @return int Number of added record
211
     */
212 54
    public function addRecord(ShapeRecord $record): int
213
    {
214 54
        if ($this->dbfHeader !== null) {
215 50
            $record->updateDBFInfo($this->dbfHeader);
216
        }
217
218 54
        $this->fileLength += $record->getContentLength() + 4;
219 54
        $this->records[] = $record;
220 54
        $this->records[count($this->records) - 1]->recordNumber = count($this->records);
221
222 54
        $this->updateBBox('x', $record->shpData);
223 54
        $this->updateBBox('y', $record->shpData);
224
225 54
        if (in_array($this->shapeType, ShapeType::MEASURED_TYPES, true)) {
226 32
            $this->updateBBox('m', $record->shpData);
227
        }
228
229 54
        if (in_array($this->shapeType, ShapeType::TYPES_WITH_Z, true)) {
230 16
            $this->updateBBox('z', $record->shpData);
231
        }
232
233 54
        return count($this->records) - 1;
234
    }
235
236
    /**
237
     * Deletes record from shapefile.
238
     */
239 2
    public function deleteRecord(int $index): void
240
    {
241 2
        if (! isset($this->records[$index])) {
242
            return;
243
        }
244
245 2
        $this->fileLength -= $this->records[$index]->getContentLength() + 4;
246 2
        $count = count($this->records) - 1;
247 2
        for ($i = $index; $i < $count; ++$i) {
248 2
            $this->records[$i] = $this->records[$i + 1];
249
        }
250
251 2
        unset($this->records[count($this->records) - 1]);
252 2
        $this->deleteRecordFromDBF($index);
253
    }
254
255
    /**
256
     * Returns array defining fields in DBF file.
257
     *
258
     * @return mixed[]|null see setDBFHeader for more information
259
     */
260 4
    public function getDBFHeader(): array|null
261
    {
262 4
        return $this->dbfHeader;
263
    }
264
265
    /**
266
     * Changes array defining fields in DBF file, used in dbase_create call.
267
     *
268
     * @param mixed[] $header An array of arrays, each array describing the
269
     *                        format of one field of the database. Each
270
     *                        field consists of a name, a character indicating
271
     *                        the field type, and optionally, a length,
272
     *                        a precision and a nullable flag.
273
     */
274 54
    public function setDBFHeader(array $header): void
275
    {
276 54
        $this->dbfHeader = $header;
277
278 54
        foreach ($this->records as $record) {
279 6
            $record->updateDBFInfo($header);
280
        }
281
    }
282
283
    /**
284
     * Lookups value in the DBF file and returns index.
285
     */
286 4
    public function getIndexFromDBFData(string $field, string $value): int
287
    {
288 4
        foreach ($this->records as $index => $record) {
289
            if (
290 4
                isset($record->dbfData[$field]) &&
291 4
                (trim(strtoupper($record->dbfData[$field])) === strtoupper($value))
292
            ) {
293 2
                return $index;
294
            }
295
        }
296
297 4
        return -1;
298
    }
299
300
    /**
301
     * Loads DBF metadata.
302
     *
303
     * @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...
304
     */
305 44
    private function loadDBFHeader(): array
306
    {
307 44
        $dbfFile = fopen($this->getFilename('.dbf'), 'r');
308 44
        if ($dbfFile === false) {
309
            return [];
310
        }
311
312 44
        $result = [];
313 44
        $i = 1;
314
315 44
        while (true) {
316 44
            if (feof($dbfFile)) {
317
                break;
318
            }
319
320 44
            $buff32 = fread($dbfFile, 32);
321 44
            if ($i > 1) {
322 44
                if (substr($buff32, 0, 1) === chr(13)) {
323 44
                    break;
324
                }
325
326 44
                $pos = strpos(substr($buff32, 0, 10), chr(0));
327 44
                $pos = ($pos === false ? 10 : $pos);
328
329 44
                $fieldName = substr($buff32, 0, $pos);
330 44
                $fieldType = substr($buff32, 11, 1);
331 44
                $fieldLen = ord(substr($buff32, 16, 1));
332 44
                $fieldDec = ord(substr($buff32, 17, 1));
333
334 44
                $result[] = [$fieldName, $fieldType, $fieldLen, $fieldDec];
335
            }
336
337 44
            ++$i;
338
        }
339
340 44
        fclose($dbfFile);
341
342 44
        return $result;
343
    }
344
345
    /**
346
     * Deletes record from the DBF file.
347
     */
348 2
    private function deleteRecordFromDBF(int $index): void
349
    {
350 2
        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

350
        if ($this->dbfFile === false || ! @/** @scrutinizer ignore-call */ dbase_delete_record($this->dbfFile, $index)) {
Loading history...
introduced by
Function dbase_delete_record() should not be referenced via a fallback global name, but via a use statement.
Loading history...
351 2
            return;
352
        }
353
354
        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

354
        /** @scrutinizer ignore-call */ 
355
        dbase_pack($this->dbfFile);
Loading history...
introduced by
Function dbase_pack() should not be referenced via a fallback global name, but via a use statement.
Loading history...
355
    }
356
357
    /**
358
     * Loads SHP file metadata.
359
     */
360 86
    private function loadHeaders(): bool
361
    {
362 86
        if (Util::loadData('N', $this->readSHP(4)) !== self::MAGIC) {
363 4
            $this->setError('Not a SHP file (file code mismatch)');
364
365 4
            return false;
366
        }
367
368
        /* Skip 20 unused bytes */
369 82
        $this->readSHP(20);
370
371 82
        $this->fileLength = (int) Util::loadData('N', $this->readSHP(4));
372
373
        /* We currently ignore version */
374 82
        $this->readSHP(4);
375
376 82
        $shapeType = Util::loadData('V', $this->readSHP(4));
377 82
        $this->shapeType = $shapeType === false ? -1 : (int) $shapeType;
378
379 82
        $this->boundingBox = [];
380 82
        $this->boundingBox['xmin'] = Util::loadData('d', $this->readSHP(8));
381 82
        $this->boundingBox['ymin'] = Util::loadData('d', $this->readSHP(8));
382 82
        $this->boundingBox['xmax'] = Util::loadData('d', $this->readSHP(8));
383 82
        $this->boundingBox['ymax'] = Util::loadData('d', $this->readSHP(8));
384 82
        $this->boundingBox['zmin'] = Util::loadData('d', $this->readSHP(8));
385 82
        $this->boundingBox['zmax'] = Util::loadData('d', $this->readSHP(8));
386 82
        $this->boundingBox['mmin'] = Util::loadData('d', $this->readSHP(8));
387 82
        $this->boundingBox['mmax'] = Util::loadData('d', $this->readSHP(8));
388
389 82
        if (self::supportsDbase()) {
390 44
            $this->dbfHeader = $this->loadDBFHeader();
391
        }
392
393 82
        return true;
394
    }
395
396
    /**
397
     * Saves bounding box record, possibly using 0 instead of not set values.
398
     *
399
     * @param resource $file File object
400
     * @param string   $type Bounding box dimension (eg. xmax, mmin...)
401
     */
402 58
    private function saveBBoxRecord($file, string $type): void
403
    {
404 58
        fwrite($file, Util::packDouble(
405 58
            $this->boundingBox[$type] ?? 0,
406 58
        ));
407
    }
408
409
    /**
410
     * Saves bounding box to a file.
411
     *
412
     * @param resource $file File object
413
     */
414 58
    private function saveBBox($file): void
415
    {
416 58
        $this->saveBBoxRecord($file, 'xmin');
417 58
        $this->saveBBoxRecord($file, 'ymin');
418 58
        $this->saveBBoxRecord($file, 'xmax');
419 58
        $this->saveBBoxRecord($file, 'ymax');
420 58
        $this->saveBBoxRecord($file, 'zmin');
421 58
        $this->saveBBoxRecord($file, 'zmax');
422 58
        $this->saveBBoxRecord($file, 'mmin');
423 58
        $this->saveBBoxRecord($file, 'mmax');
424
    }
425
426
    /**
427
     * Saves SHP and SHX file metadata.
428
     */
429 58
    private function saveHeaders(): void
430
    {
431 58
        if ($this->shpFile === false) {
432
            return;
433
        }
434
435 58
        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

435
        fwrite(/** @scrutinizer ignore-type */ $this->shpFile, pack('NNNNNN', self::MAGIC, 0, 0, 0, 0, 0));
Loading history...
436 58
        fwrite($this->shpFile, pack('N', $this->fileLength));
437 58
        fwrite($this->shpFile, pack('V', 1000));
438 58
        fwrite($this->shpFile, pack('V', $this->shapeType));
439 58
        $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

439
        $this->saveBBox(/** @scrutinizer ignore-type */ $this->shpFile);
Loading history...
440
441 58
        if ($this->shxFile === false) {
442
            return;
443
        }
444
445 58
        fwrite($this->shxFile, pack('NNNNNN', self::MAGIC, 0, 0, 0, 0, 0));
446 58
        fwrite($this->shxFile, pack('N', 50 + 4 * count($this->records)));
447 58
        fwrite($this->shxFile, pack('V', 1000));
448 58
        fwrite($this->shxFile, pack('V', $this->shapeType));
449 58
        $this->saveBBox($this->shxFile);
450
    }
451
452
    /**
453
     * Loads records from SHP file (and DBF).
454
     */
455 82
    private function loadRecords(): bool
456
    {
457
        /* Need to start at offset 100 */
458 82
        while (! $this->eofSHP()) {
459 82
            $record = new ShapeRecord(-1);
460 82
            $record->loadFromFile($this, $this->dbfFile);
461 82
            if ($record->lastError !== '') {
462
                $this->setError($record->lastError);
463
464
                return false;
465
            }
466
467 82
            if (($record->shapeType === -1) && $this->eofSHP()) {
468 82
                break;
469
            }
470
471 82
            $this->records[] = $record;
472
        }
473
474 82
        return true;
475
    }
476
477
    /**
478
     * Saves records to SHP and SHX files.
479
     */
480 58
    private function saveRecords(): void
481
    {
482 58
        $offset = 50;
483 58
        if ($this->records === [] || $this->shxFile === false || $this->shpFile === false) {
484 4
            return;
485
        }
486
487 54
        foreach ($this->records as $index => $record) {
488
            //Save the record to the .shp file
489 54
            $record->saveToFile($this->shpFile, $this->dbfFile, $index + 1);
490
491
            //Save the record to the .shx file
492 54
            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

492
            fwrite(/** @scrutinizer ignore-type */ $this->shxFile, pack('N', $offset));
Loading history...
493 54
            fwrite($this->shxFile, pack('N', $record->getContentLength()));
494 54
            $offset += 4 + $record->getContentLength();
495
        }
496
    }
497
498
    /**
499
     * Generic interface to open files.
500
     *
501
     * @param bool   $toWrite   Whether file should be opened for writing
502
     * @param string $extension File extension
503
     * @param string $name      Verbose file name to report errors
504
     *
505
     * @return resource|false File handle
506
     */
507 102
    private function openFile(bool $toWrite, string $extension, string $name)
508
    {
509 102
        $shpName = $this->getFilename($extension);
510 102
        $result = @fopen($shpName, ($toWrite ? 'wb+' : 'rb'));
511 102
        if (! $result) {
0 ignored issues
show
introduced by
$result is of type false|resource, thus it always evaluated to false.
Loading history...
512 8
            $this->setError(sprintf('It wasn\'t possible to open the %s file "%s"', $name, $shpName));
513
514 8
            return false;
515
        }
516
517 94
        return $result;
518
    }
519
520
    /**
521
     * Opens SHP file.
522
     *
523
     * @param bool $toWrite Whether file should be opened for writing
524
     */
525 102
    private function openSHPFile(bool $toWrite = false): bool
526
    {
527 102
        $this->shpFile = $this->openFile($toWrite, '.shp', 'Shape');
528
529 102
        return (bool) $this->shpFile;
530
    }
531
532
    /**
533
     * Closes SHP file.
534
     */
535 90
    private function closeSHPFile(): void
536
    {
537 90
        if ($this->shpFile === false) {
538 2
            return;
539
        }
540
541 88
        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

541
        fclose(/** @scrutinizer ignore-type */ $this->shpFile);
Loading history...
542 88
        $this->shpFile = false;
543
    }
544
545
    /**
546
     * Opens SHX file.
547
     *
548
     * @param bool $toWrite Whether file should be opened for writing
549
     */
550 58
    private function openSHXFile(bool $toWrite = false): bool
551
    {
552 58
        $this->shxFile = $this->openFile($toWrite, '.shx', 'Index');
553
554 58
        return (bool) $this->shxFile;
555
    }
556
557
    /**
558
     * Closes SHX file.
559
     */
560 58
    private function closeSHXFile(): void
561
    {
562 58
        if ($this->shxFile === false) {
563
            return;
564
        }
565
566 58
        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

566
        fclose(/** @scrutinizer ignore-type */ $this->shxFile);
Loading history...
567 58
        $this->shxFile = false;
568
    }
569
570
    /**
571
     * Creates DBF file.
572
     */
573 58
    private function createDBFFile(): bool
574
    {
575 58
        if (! self::supportsDbase() || $this->dbfHeader === null || $this->dbfHeader === []) {
576 28
            $this->dbfFile = false;
577
578 28
            return true;
579
        }
580
581 30
        $dbfName = $this->getFilename('.dbf');
582
583
        /* Unlink existing file */
584 30
        if (file_exists($dbfName)) {
585 4
            unlink($dbfName);
586
        }
587
588
        /* Create new file */
589 30
        $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

589
        $this->dbfFile = @/** @scrutinizer ignore-call */ dbase_create($dbfName, $this->dbfHeader);
Loading history...
introduced by
Function dbase_create() should not be referenced via a fallback global name, but via a use statement.
Loading history...
590 30
        if ($this->dbfFile === false) {
591
            $this->setError(sprintf('It wasn\'t possible to create the DBase file "%s"', $dbfName));
592
593
            return false;
594
        }
595
596 30
        return true;
597
    }
598
599
    /**
600
     * Loads DBF file if supported.
601
     */
602 94
    private function openDBFFile(): bool
603
    {
604 94
        if (! self::supportsDbase()) {
605 42
            $this->dbfFile = false;
606
607 42
            return true;
608
        }
609
610 52
        $dbfName = $this->getFilename('.dbf');
611 52
        if (! is_readable($dbfName)) {
612 6
            $this->setError(sprintf('It wasn\'t possible to find the DBase file "%s"', $dbfName));
613
614 6
            return false;
615
        }
616
617 46
        $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

617
        $this->dbfFile = @/** @scrutinizer ignore-call */ dbase_open($dbfName, 0);
Loading history...
introduced by
Function dbase_open() should not be referenced via a fallback global name, but via a use statement.
Loading history...
618 46
        if ($this->dbfFile === false) {
619 2
            $this->setError(sprintf('It wasn\'t possible to open the DBase file "%s"', $dbfName));
620
621 2
            return false;
622
        }
623
624 44
        return true;
625
    }
626
627
    /**
628
     * Closes DBF file.
629
     */
630 90
    private function closeDBFFile(): void
631
    {
632 90
        if ($this->dbfFile === false) {
633 46
            return;
634
        }
635
636 44
        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

636
        /** @scrutinizer ignore-call */ 
637
        dbase_close($this->dbfFile);
Loading history...
introduced by
Function dbase_close() should not be referenced via a fallback global name, but via a use statement.
Loading history...
637 44
        $this->dbfFile = false;
638
    }
639
640
    /**
641
     * Sets error message.
642
     */
643 20
    public function setError(string $error): void
644
    {
645 20
        $this->lastError = $error;
646
    }
647
648
    /**
649
     * Reads given number of bytes from SHP file.
650
     *
651
     * @param int<0, max> $bytes
652
     */
653 86
    public function readSHP(int $bytes): string|false
654
    {
655 86
        if ($this->shpFile === false) {
656 2
            return false;
657
        }
658
659 84
        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

659
        return fread(/** @scrutinizer ignore-type */ $this->shpFile, $bytes);
Loading history...
660
    }
661
662
    /**
663
     * Checks whether file is at EOF.
664
     */
665 82
    public function eofSHP(): bool
666
    {
667 82
        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

667
        return feof(/** @scrutinizer ignore-type */ $this->shpFile);
Loading history...
668
    }
669
670
    /**
671
     * Returns shape name.
672
     *
673
     * @psalm-return non-empty-string
674
     */
675 4
    public function getShapeName(): string
676
    {
677 4
        return ShapeType::name($this->shapeType);
678
    }
679
680
    /**
681
     * Check whether file contains measure data.
682
     *
683
     * For some reason this is distinguished by zero bounding box in the
684
     * specification.
685
     */
686 32
    public function hasMeasure(): bool
687
    {
688
        // phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators.DisallowedNotEqualOperator
689 32
        return $this->boundingBox['mmin'] != 0 || $this->boundingBox['mmax'] != 0;
690
    }
691
}
692