ImportShp.php 10 KB

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