"Fossies" - the Fresh Open Source Software Archive 
Member "phpMyAdmin-5.1.0-english/libraries/classes/Plugins/Import/ImportShp.php" (24 Feb 2021, 11027 Bytes) of package /linux/www/phpMyAdmin-5.1.0-english.zip:
1 <?php
2 /**
3 * ESRI Shape file import plugin for phpMyAdmin
4 */
5
6 declare(strict_types=1);
7
8 namespace PhpMyAdmin\Plugins\Import;
9
10 use PhpMyAdmin\File;
11 use PhpMyAdmin\Gis\GisFactory;
12 use PhpMyAdmin\Gis\GisMultiLineString;
13 use PhpMyAdmin\Gis\GisMultiPoint;
14 use PhpMyAdmin\Gis\GisPoint;
15 use PhpMyAdmin\Gis\GisPolygon;
16 use PhpMyAdmin\Import;
17 use PhpMyAdmin\Message;
18 use PhpMyAdmin\Plugins\ImportPlugin;
19 use PhpMyAdmin\Properties\Plugins\ImportPluginProperties;
20 use PhpMyAdmin\Sanitize;
21 use PhpMyAdmin\ZipExtension;
22 use ZipArchive;
23 use const LOCK_EX;
24 use function count;
25 use function extension_loaded;
26 use function file_exists;
27 use function file_put_contents;
28 use function mb_strlen;
29 use function mb_substr;
30 use function pathinfo;
31 use function strcmp;
32 use function strlen;
33 use function substr;
34 use function trim;
35 use function unlink;
36
37 /**
38 * Handles the import for ESRI Shape files
39 */
40 class ImportShp extends ImportPlugin
41 {
42 /** @var ZipExtension */
43 private $zipExtension;
44
45 public function __construct()
46 {
47 parent::__construct();
48 $this->setProperties();
49 if (! extension_loaded('zip')) {
50 return;
51 }
52
53 $this->zipExtension = new ZipExtension(new ZipArchive());
54 }
55
56 /**
57 * Sets the import plugin properties.
58 * Called in the constructor.
59 *
60 * @return void
61 */
62 protected function setProperties()
63 {
64 $importPluginProperties = new ImportPluginProperties();
65 $importPluginProperties->setText(__('ESRI Shape File'));
66 $importPluginProperties->setExtension('shp');
67 $importPluginProperties->setOptions([]);
68 $importPluginProperties->setOptionsText(__('Options'));
69
70 $this->properties = $importPluginProperties;
71 }
72
73 /**
74 * Handles the whole import logic
75 *
76 * @param array $sql_data 2-element array with sql data
77 *
78 * @return void
79 */
80 public function doImport(?File $importHandle = null, array &$sql_data = [])
81 {
82 global $db, $error, $finished, $import_file, $local_import_file, $message, $dbi;
83
84 $GLOBALS['finished'] = false;
85
86 if ($importHandle === null) {
87 return;
88 }
89
90 /** @see ImportShp::readFromBuffer() */
91 $GLOBALS['importHandle'] = $importHandle;
92
93 $compression = $importHandle->getCompression();
94
95 $shp = new ShapeFileImport(1);
96 // If the zip archive has more than one file,
97 // get the correct content to the buffer from .shp file.
98 if ($compression === 'application/zip'
99 && $this->zipExtension->getNumberOfFiles($import_file) > 1
100 ) {
101 if ($importHandle->openZip('/^.*\.shp$/i') === false) {
102 $message = Message::error(
103 __('There was an error importing the ESRI shape file: "%s".')
104 );
105 $message->addParam($importHandle->getError());
106
107 return;
108 }
109 }
110
111 $temp_dbf_file = false;
112 // We need dbase extension to handle .dbf file
113 if (extension_loaded('dbase')) {
114 $temp = $GLOBALS['PMA_Config']->getTempDir('shp');
115 // If we can extract the zip archive to 'TempDir'
116 // and use the files in it for import
117 if ($compression === 'application/zip' && $temp !== null) {
118 $dbf_file_name = $this->zipExtension->findFile(
119 $import_file,
120 '/^.*\.dbf$/i'
121 );
122 // If the corresponding .dbf file is in the zip archive
123 if ($dbf_file_name) {
124 // Extract the .dbf file and point to it.
125 $extracted = $this->zipExtension->extract(
126 $import_file,
127 $dbf_file_name
128 );
129 if ($extracted !== false) {
130 // remove filename extension, e.g.
131 // dresden_osm.shp/gis.osm_transport_a_v06.dbf
132 // to
133 // dresden_osm.shp/gis.osm_transport_a_v06
134 $path_parts = pathinfo($dbf_file_name);
135 $dbf_file_name = $path_parts['dirname'] . '/' . $path_parts['filename'];
136
137 // sanitize filename
138 $dbf_file_name = Sanitize::sanitizeFilename($dbf_file_name, true);
139
140 // concat correct filename and extension
141 $dbf_file_path = $temp . '/' . $dbf_file_name . '.dbf';
142
143 if (file_put_contents($dbf_file_path, $extracted, LOCK_EX) !== false) {
144 $temp_dbf_file = true;
145
146 // Replace the .dbf with .*, as required by the bsShapeFiles library.
147 $shp->FileName = substr($dbf_file_path, 0, -4) . '.*';
148 }
149 }
150 }
151 } elseif (! empty($local_import_file)
152 && ! empty($GLOBALS['cfg']['UploadDir'])
153 && $compression === 'none'
154 ) {
155 // If file is in UploadDir, use .dbf file in the same UploadDir
156 // to load extra data.
157 // Replace the .shp with .*,
158 // so the bsShapeFiles library correctly locates .dbf file.
159 $file_name = mb_substr(
160 $import_file,
161 0,
162 mb_strlen($import_file) - 4
163 ) . '.*';
164 $shp->FileName = $file_name;
165 }
166 }
167
168 // It should load data before file being deleted
169 $shp->loadFromFile('');
170
171 // Delete the .dbf file extracted to 'TempDir'
172 if ($temp_dbf_file
173 && isset($dbf_file_path)
174 && @file_exists($dbf_file_path)
175 ) {
176 unlink($dbf_file_path);
177 }
178
179 if ($shp->lastError != '') {
180 $error = true;
181 $message = Message::error(
182 __('There was an error importing the ESRI shape file: "%s".')
183 );
184 $message->addParam($shp->lastError);
185
186 return;
187 }
188
189 switch ($shp->shapeType) {
190 // ESRI Null Shape
191 case 0:
192 break;
193 // ESRI Point
194 case 1:
195 $gis_type = 'point';
196 break;
197 // ESRI PolyLine
198 case 3:
199 $gis_type = 'multilinestring';
200 break;
201 // ESRI Polygon
202 case 5:
203 $gis_type = 'multipolygon';
204 break;
205 // ESRI MultiPoint
206 case 8:
207 $gis_type = 'multipoint';
208 break;
209 default:
210 $error = true;
211 $message = Message::error(
212 __('MySQL Spatial Extension does not support ESRI type "%s".')
213 );
214 $message->addParam($shp->getShapeName());
215
216 return;
217 }
218
219 if (isset($gis_type)) {
220 /** @var GisMultiLineString|GisMultiPoint|GisPoint|GisPolygon $gis_obj */
221 $gis_obj = GisFactory::factory($gis_type);
222 } else {
223 $gis_obj = null;
224 }
225
226 $num_rows = count($shp->records);
227 // If .dbf file is loaded, the number of extra data columns
228 $num_data_cols = $shp->getDBFHeader() !== null ? count($shp->getDBFHeader()) : 0;
229
230 $rows = [];
231 $col_names = [];
232 if ($num_rows != 0) {
233 foreach ($shp->records as $record) {
234 $tempRow = [];
235 if ($gis_obj == null) {
236 $tempRow[] = null;
237 } else {
238 $tempRow[] = "GeomFromText('"
239 . $gis_obj->getShape($record->SHPData) . "')";
240 }
241
242 if ($shp->getDBFHeader() !== null) {
243 foreach ($shp->getDBFHeader() as $c) {
244 $cell = trim((string) $record->DBFData[$c[0]]);
245
246 if (! strcmp($cell, '')) {
247 $cell = 'NULL';
248 }
249
250 $tempRow[] = $cell;
251 }
252 }
253 $rows[] = $tempRow;
254 }
255 }
256
257 if (count($rows) === 0) {
258 $error = true;
259 $message = Message::error(
260 __('The imported file does not contain any data!')
261 );
262
263 return;
264 }
265
266 // Column names for spatial column and the rest of the columns,
267 // if they are available
268 $col_names[] = 'SPATIAL';
269 for ($n = 0; $n < $num_data_cols; $n++) {
270 $col_names[] = $shp->getDBFHeader()[$n][0];
271 }
272
273 // Set table name based on the number of tables
274 if (strlen((string) $db) > 0) {
275 $result = $dbi->fetchResult('SHOW TABLES');
276 $table_name = 'TABLE ' . (count($result) + 1);
277 } else {
278 $table_name = 'TBL_NAME';
279 }
280 $tables = [
281 [
282 $table_name,
283 $col_names,
284 $rows,
285 ],
286 ];
287
288 // Use data from shape file to chose best-fit MySQL types for each column
289 $analyses = [];
290 $analyses[] = $this->import->analyzeTable($tables[0]);
291
292 $table_no = 0;
293 $spatial_col = 0;
294 $analyses[$table_no][Import::TYPES][$spatial_col] = Import::GEOMETRY;
295 $analyses[$table_no][Import::FORMATTEDSQL][$spatial_col] = true;
296
297 // Set database name to the currently selected one, if applicable
298 if (strlen((string) $db) > 0) {
299 $db_name = $db;
300 $options = ['create_db' => false];
301 } else {
302 $db_name = 'SHP_DB';
303 $options = null;
304 }
305
306 // Created and execute necessary SQL statements from data
307 $null_param = null;
308 $this->import->buildSql($db_name, $tables, $analyses, $null_param, $options, $sql_data);
309
310 unset($tables, $analyses);
311
312 $finished = true;
313 $error = false;
314
315 // Commit any possible data in buffers
316 $this->import->runQuery('', '', $sql_data);
317 }
318
319 /**
320 * Returns specified number of bytes from the buffer.
321 * Buffer automatically fetches next chunk of data when the buffer
322 * falls short.
323 * Sets $eof when $GLOBALS['finished'] is set and the buffer falls short.
324 *
325 * @param int $length number of bytes
326 *
327 * @return string
328 */
329 public static function readFromBuffer($length)
330 {
331 global $buffer, $eof, $importHandle;
332
333 $import = new Import();
334
335 if (strlen((string) $buffer) < $length) {
336 if ($GLOBALS['finished']) {
337 $eof = true;
338 } else {
339 $buffer .= $import->getNextChunk($importHandle);
340 }
341 }
342 $result = substr($buffer, 0, $length);
343 $buffer = substr($buffer, $length);
344
345 return $result;
346 }
347 }