3 * ESRI Shape file import plugin for phpMyAdmin
6 declare(strict_types
=1);
8 namespace PhpMyAdmin\Plugins\Import
;
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
;
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
;
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')) {
54 $this->zipExtension
= new ZipExtension(new ZipArchive());
58 * @psalm-return non-empty-lowercase-string
60 public function getName(): string
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;
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) {
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());
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
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'];
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) . '.*';
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
);
172 switch ($shp->shapeType
) {
182 $gis_type = 'multilinestring';
186 $gis_type = 'multipolygon';
190 $gis_type = 'multipoint';
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());
202 if (isset($gis_type)) {
203 /** @var GisMultiLineString|GisMultiPoint|GisPoint|GisPolygon $gis_obj */
204 $gis_obj = GisFactory
::factory($gis_type);
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;
215 if ($num_rows != 0) {
216 foreach ($shp->records
as $record) {
218 if ($gis_obj == null ||
! method_exists($gis_obj, 'getShape')) {
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, '')) {
241 if (count($rows) === 0) {
242 $GLOBALS['error'] = true;
243 $GLOBALS['message'] = Message
::error(
244 __('The imported file does not contain any data!')
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) {
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);
267 $table_name = 'TBL_NAME';
278 // Use data from shape file to chose best-fit MySQL types for each column
280 $analyses[] = $this->import
->analyzeTable($tables[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];
296 // Created and execute necessary SQL statements from data
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
313 * Sets $eof when $GLOBALS['finished'] is set and the buffer falls short.
315 * @param int $length number of bytes
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;
327 $GLOBALS['buffer'] .= $import->getNextChunk($GLOBALS['importHandle']);
331 $result = substr($GLOBALS['buffer'], 0, $length);
332 $GLOBALS['buffer'] = substr($GLOBALS['buffer'], $length);