Replace `global` keyword with `$GLOBALS`
[phpmyadmin.git] / libraries / classes / Plugins / Import / ImportShp.php
blob9cc44304862cefd55193194b2233fdc94dc8bbcf
1 <?php
2 /**
3 * ESRI Shape file import plugin for phpMyAdmin
4 */
6 declare(strict_types=1);
8 namespace PhpMyAdmin\Plugins\Import;
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;
24 use function __;
25 use function count;
26 use function extension_loaded;
27 use function file_exists;
28 use function file_put_contents;
29 use function mb_substr;
30 use function method_exists;
31 use function pathinfo;
32 use function strcmp;
33 use function strlen;
34 use function substr;
35 use function trim;
36 use function unlink;
38 use const LOCK_EX;
40 /**
41 * Handles the import for ESRI Shape files
43 class ImportShp extends ImportPlugin
45 /** @var ZipExtension|null */
46 private $zipExtension = null;
48 protected function init(): void
50 if (! extension_loaded('zip')) {
51 return;
54 $this->zipExtension = new ZipExtension(new ZipArchive());
57 /**
58 * @psalm-return non-empty-lowercase-string
60 public function getName(): string
62 return 'shp';
65 protected function setProperties(): ImportPluginProperties
67 $importPluginProperties = new ImportPluginProperties();
68 $importPluginProperties->setText(__('ESRI Shape File'));
69 $importPluginProperties->setExtension('shp');
70 $importPluginProperties->setOptionsText(__('Options'));
72 return $importPluginProperties;
75 /**
76 * Handles the whole import logic
78 * @param array $sql_data 2-element array with sql data
80 public function doImport(?File $importHandle = null, array &$sql_data = []): void
82 $GLOBALS['finished'] = false;
84 if ($importHandle === null || $this->zipExtension === null) {
85 return;
88 /** @see ImportShp::readFromBuffer() */
89 $GLOBALS['importHandle'] = $importHandle;
91 $compression = $importHandle->getCompression();
93 $shp = new ShapeFileImport(1);
94 // If the zip archive has more than one file,
95 // get the correct content to the buffer from .shp file.
96 if ($compression === 'application/zip' && $this->zipExtension->getNumberOfFiles($GLOBALS['import_file']) > 1) {
97 if ($importHandle->openZip('/^.*\.shp$/i') === false) {
98 $GLOBALS['message'] = Message::error(
99 __('There was an error importing the ESRI shape file: "%s".')
101 $GLOBALS['message']->addParam($importHandle->getError());
103 return;
107 $temp_dbf_file = false;
108 // We need dbase extension to handle .dbf file
109 if (extension_loaded('dbase')) {
110 $temp = $GLOBALS['config']->getTempDir('shp');
111 // If we can extract the zip archive to 'TempDir'
112 // and use the files in it for import
113 if ($compression === 'application/zip' && $temp !== null) {
114 $dbf_file_name = $this->zipExtension->findFile($GLOBALS['import_file'], '/^.*\.dbf$/i');
115 // If the corresponding .dbf file is in the zip archive
116 if ($dbf_file_name) {
117 // Extract the .dbf file and point to it.
118 $extracted = $this->zipExtension->extract($GLOBALS['import_file'], $dbf_file_name);
119 if ($extracted !== false) {
120 // remove filename extension, e.g.
121 // dresden_osm.shp/gis.osm_transport_a_v06.dbf
122 // to
123 // dresden_osm.shp/gis.osm_transport_a_v06
124 $path_parts = pathinfo($dbf_file_name);
125 $dbf_file_name = $path_parts['dirname'] . '/' . $path_parts['filename'];
127 // sanitize filename
128 $dbf_file_name = Sanitize::sanitizeFilename($dbf_file_name, true);
130 // concat correct filename and extension
131 $dbf_file_path = $temp . '/' . $dbf_file_name . '.dbf';
133 if (file_put_contents($dbf_file_path, $extracted, LOCK_EX) !== false) {
134 $temp_dbf_file = true;
136 // Replace the .dbf with .*, as required by the bsShapeFiles library.
137 $shp->fileName = substr($dbf_file_path, 0, -4) . '.*';
141 } elseif (
142 ! empty($GLOBALS['local_import_file'])
143 && ! empty($GLOBALS['cfg']['UploadDir'])
144 && $compression === 'none'
146 // If file is in UploadDir, use .dbf file in the same UploadDir
147 // to load extra data.
148 // Replace the .shp with .*,
149 // so the bsShapeFiles library correctly locates .dbf file.
150 $shp->fileName = mb_substr($GLOBALS['import_file'], 0, -4) . '.*';
154 // It should load data before file being deleted
155 $shp->loadFromFile('');
157 // Delete the .dbf file extracted to 'TempDir'
158 if ($temp_dbf_file && isset($dbf_file_path) && @file_exists($dbf_file_path)) {
159 unlink($dbf_file_path);
162 if ($shp->lastError != '') {
163 $GLOBALS['error'] = true;
164 $GLOBALS['message'] = Message::error(
165 __('There was an error importing the ESRI shape file: "%s".')
167 $GLOBALS['message']->addParam($shp->lastError);
169 return;
172 switch ($shp->shapeType) {
173 // ESRI Null Shape
174 case 0:
175 break;
176 // ESRI Point
177 case 1:
178 $gis_type = 'point';
179 break;
180 // ESRI PolyLine
181 case 3:
182 $gis_type = 'multilinestring';
183 break;
184 // ESRI Polygon
185 case 5:
186 $gis_type = 'multipolygon';
187 break;
188 // ESRI MultiPoint
189 case 8:
190 $gis_type = 'multipoint';
191 break;
192 default:
193 $GLOBALS['error'] = true;
194 $GLOBALS['message'] = Message::error(
195 __('MySQL Spatial Extension does not support ESRI type "%s".')
197 $GLOBALS['message']->addParam($shp->getShapeName());
199 return;
202 if (isset($gis_type)) {
203 /** @var GisMultiLineString|GisMultiPoint|GisPoint|GisPolygon $gis_obj */
204 $gis_obj = GisFactory::factory($gis_type);
205 } else {
206 $gis_obj = null;
209 $num_rows = count($shp->records);
210 // If .dbf file is loaded, the number of extra data columns
211 $num_data_cols = $shp->getDBFHeader() !== null ? count($shp->getDBFHeader()) : 0;
213 $rows = [];
214 $col_names = [];
215 if ($num_rows != 0) {
216 foreach ($shp->records as $record) {
217 $tempRow = [];
218 if ($gis_obj == null || ! method_exists($gis_obj, 'getShape')) {
219 $tempRow[] = null;
220 } else {
221 $tempRow[] = "GeomFromText('"
222 . $gis_obj->getShape($record->shpData) . "')";
225 if ($shp->getDBFHeader() !== null) {
226 foreach ($shp->getDBFHeader() as $c) {
227 $cell = trim((string) $record->dbfData[$c[0]]);
229 if (! strcmp($cell, '')) {
230 $cell = 'NULL';
233 $tempRow[] = $cell;
237 $rows[] = $tempRow;
241 if (count($rows) === 0) {
242 $GLOBALS['error'] = true;
243 $GLOBALS['message'] = Message::error(
244 __('The imported file does not contain any data!')
247 return;
250 // Column names for spatial column and the rest of the columns,
251 // if they are available
252 $col_names[] = 'SPATIAL';
253 $dbfHeader = $shp->getDBFHeader();
254 for ($n = 0; $n < $num_data_cols; $n++) {
255 if ($dbfHeader === null) {
256 continue;
259 $col_names[] = $dbfHeader[$n][0];
262 // Set table name based on the number of tables
263 if (strlen((string) $GLOBALS['db']) > 0) {
264 $result = $GLOBALS['dbi']->fetchResult('SHOW TABLES');
265 $table_name = 'TABLE ' . (count($result) + 1);
266 } else {
267 $table_name = 'TBL_NAME';
270 $tables = [
272 $table_name,
273 $col_names,
274 $rows,
278 // Use data from shape file to chose best-fit MySQL types for each column
279 $analyses = [];
280 $analyses[] = $this->import->analyzeTable($tables[0]);
282 $table_no = 0;
283 $spatial_col = 0;
284 $analyses[$table_no][Import::TYPES][$spatial_col] = Import::GEOMETRY;
285 $analyses[$table_no][Import::FORMATTEDSQL][$spatial_col] = true;
287 // Set database name to the currently selected one, if applicable
288 if (strlen((string) $GLOBALS['db']) > 0) {
289 $db_name = $GLOBALS['db'];
290 $options = ['create_db' => false];
291 } else {
292 $db_name = 'SHP_DB';
293 $options = null;
296 // Created and execute necessary SQL statements from data
297 $null_param = null;
298 $this->import->buildSql($db_name, $tables, $analyses, $null_param, $options, $sql_data);
300 unset($tables, $analyses);
302 $GLOBALS['finished'] = true;
303 $GLOBALS['error'] = false;
305 // Commit any possible data in buffers
306 $this->import->runQuery('', '', $sql_data);
310 * Returns specified number of bytes from the buffer.
311 * Buffer automatically fetches next chunk of data when the buffer
312 * falls short.
313 * Sets $eof when $GLOBALS['finished'] is set and the buffer falls short.
315 * @param int $length number of bytes
317 * @return string
319 public static function readFromBuffer($length)
321 $import = new Import();
323 if (strlen((string) $GLOBALS['buffer']) < $length) {
324 if ($GLOBALS['finished']) {
325 $GLOBALS['eof'] = true;
326 } else {
327 $GLOBALS['buffer'] .= $import->getNextChunk($GLOBALS['importHandle']);
331 $result = substr($GLOBALS['buffer'], 0, $length);
332 $GLOBALS['buffer'] = substr($GLOBALS['buffer'], $length);
334 return $result;