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
![]() |
|||||||
31 | use function dbase_create; |
||||||
0 ignored issues
–
show
|
|||||||
32 | use function dbase_delete_record; |
||||||
0 ignored issues
–
show
|
|||||||
33 | use function dbase_open; |
||||||
0 ignored issues
–
show
|
|||||||
34 | use function dbase_pack; |
||||||
0 ignored issues
–
show
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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
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
![]() |
|||||||
372 | 3 | return; |
|||||
373 | } |
||||||
374 | |||||||
375 | dbase_pack($this->dbfFile); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
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
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
![]() |
|||||||
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
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
![]() |
|||||||
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
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
![]() |
|||||||
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
|
|||||||
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
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
![]() |
|||||||
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
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
![]() |
|||||||
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
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
![]() |
|||||||
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
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
![]() |
|||||||
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
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
![]() |
|||||||
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
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
![]() |
|||||||
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
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
![]() |
|||||||
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 |