PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/libraries/classes/Plugins/Import/ImportShp.php

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