Passed
Push — master ( ebc7bd...b870e4 )
by Maurício
16:37 queued 05:33
created

ImportShp::doImport()   F

Complexity

Conditions 38
Paths > 20000

Size

Total Lines 215
Code Lines 116

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 76
CRAP Score 97.1928

Importance

Changes 0
Metric Value
eloc 116
dl 0
loc 215
ccs 76
cts 116
cp 0.6552
rs 0
c 0
b 0
f 0
cc 38
nc 70058
nop 1
crap 97.1928

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Config;
11
use PhpMyAdmin\Current;
12
use PhpMyAdmin\File;
13
use PhpMyAdmin\Gis\GisFactory;
14
use PhpMyAdmin\Gis\GisMultiLineString;
15
use PhpMyAdmin\Gis\GisMultiPoint;
16
use PhpMyAdmin\Gis\GisPoint;
17
use PhpMyAdmin\Gis\GisPolygon;
18
use PhpMyAdmin\Http\ServerRequest;
19
use PhpMyAdmin\Import\ColumnType;
20
use PhpMyAdmin\Import\Import;
21
use PhpMyAdmin\Import\ImportSettings;
22
use PhpMyAdmin\Import\ImportTable;
23
use PhpMyAdmin\Message;
24
use PhpMyAdmin\Plugins\ImportPlugin;
25
use PhpMyAdmin\Properties\Plugins\ImportPluginProperties;
26
use PhpMyAdmin\Sanitize;
27
use PhpMyAdmin\ShapeFile\ShapeType;
28
use PhpMyAdmin\ZipExtension;
29
use ZipArchive;
30
31
use function __;
32
use function count;
33
use function extension_loaded;
34
use function file_exists;
35
use function file_put_contents;
36
use function mb_substr;
37
use function method_exists;
38
use function pathinfo;
39
use function strlen;
40
use function substr;
41
use function trim;
42
use function unlink;
43
44
use const LOCK_EX;
45
use const PATHINFO_FILENAME;
46
47
/**
48
 * Handles the import for ESRI Shape files
49
 */
50
class ImportShp extends ImportPlugin
51
{
52
    private ZipExtension|null $zipExtension = null;
53
    private static File|null $importHandle = null;
54
    private static string $buffer = '';
55
    public static bool $eof = false;
56
57 12
    protected function init(): void
58
    {
59 12
        if (! extension_loaded('zip')) {
60
            return;
61
        }
62
63 12
        $this->zipExtension = new ZipExtension(new ZipArchive());
64
    }
65
66
    /** @psalm-return non-empty-lowercase-string */
67
    public function getName(): string
68
    {
69
        return 'shp';
70
    }
71
72 12
    protected function setProperties(): ImportPluginProperties
73
    {
74 12
        $importPluginProperties = new ImportPluginProperties();
75 12
        $importPluginProperties->setText(__('ESRI Shape File'));
76 12
        $importPluginProperties->setExtension('shp');
77 12
        $importPluginProperties->setOptionsText(__('Options'));
78
79 12
        return $importPluginProperties;
80
    }
81
82
    public function setImportOptions(ServerRequest $request): void
83
    {
84
    }
85
86
    /**
87
     * Handles the whole import logic
88
     *
89
     * @return string[]
90
     */
91 8
    public function doImport(File|null $importHandle = null): array
92
    {
93 8
        ImportSettings::$finished = false;
94
95 8
        if ($importHandle === null || $this->zipExtension === null) {
96
            return [];
97
        }
98
99 8
        self::$importHandle = $importHandle;
100
101 8
        $compression = $importHandle->getCompression();
102
103 8
        $shp = new ShapeFileImport(ShapeType::Point);
104
        // If the zip archive has more than one file,
105
        // get the correct content to the buffer from .shp file.
106
        if (
107 8
            $compression === 'application/zip'
108 8
            && $this->zipExtension->getNumberOfFiles(ImportSettings::$importFile) > 1
109
        ) {
110 8
            if ($importHandle->openZip('/^.*\.shp$/i') === false) {
111
                Current::$message = Message::error(
112
                    __('There was an error importing the ESRI shape file: "%s".'),
113
                );
114
                Current::$message->addParam($importHandle->getError());
115
116
                return [];
117
            }
118
        }
119
120 8
        $tempDbfFile = false;
121
        // We need dbase extension to handle .dbf file
122 8
        if (extension_loaded('dbase')) {
123 2
            $config = Config::getInstance();
0 ignored issues
show
Deprecated Code introduced by
The function PhpMyAdmin\Config::getInstance() has been deprecated: Use dependency injection instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

123
            $config = /** @scrutinizer ignore-deprecated */ Config::getInstance();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
124 2
            $temp = $config->getTempDir('shp');
125
            // If we can extract the zip archive to 'TempDir'
126
            // and use the files in it for import
127 2
            if ($compression === 'application/zip' && $temp !== null) {
128 2
                $dbfFileName = $this->zipExtension->findFile(ImportSettings::$importFile, '/^.*\.dbf$/i');
129
                // If the corresponding .dbf file is in the zip archive
130 2
                if ($dbfFileName !== false) {
131
                    // Extract the .dbf file and point to it.
132 2
                    $extracted = $this->zipExtension->extract(ImportSettings::$importFile, $dbfFileName);
133 2
                    if ($extracted !== false) {
134
                        // remove filename extension, e.g.
135
                        // dresden_osm.shp/gis.osm_transport_a_v06.dbf
136
                        // to
137
                        // dresden_osm.shp/gis.osm_transport_a_v06
138 2
                        $pathParts = pathinfo($dbfFileName);
139 2
                        $dbfFileName = $pathParts['dirname'] . '/' . $pathParts['filename'];
140
141
                        // sanitize filename
142 2
                        $dbfFileName = Sanitize::sanitizeFilename($dbfFileName, true);
143
144
                        // concat correct filename and extension
145 2
                        $dbfFilePath = $temp . '/' . $dbfFileName . '.dbf';
146
147 2
                        if (file_put_contents($dbfFilePath, $extracted, LOCK_EX) !== false) {
148 2
                            $tempDbfFile = true;
149
150
                            // Replace the .dbf with .*, as required by the bsShapeFiles library.
151 2
                            $shp->fileName = substr($dbfFilePath, 0, -4) . '.*';
152
                        }
153
                    }
154
                }
155
            } elseif (
156
                ImportSettings::$localImportFile !== ''
157
                && ! empty($config->settings['UploadDir'])
158
                && $compression === 'none'
159
            ) {
160
                // If file is in UploadDir, use .dbf file in the same UploadDir
161
                // to load extra data.
162
                // Replace the .shp with .*,
163
                // so the bsShapeFiles library correctly locates .dbf file.
164
                $shp->fileName = mb_substr(ImportSettings::$importFile, 0, -4) . '.*';
165
            }
166
        }
167
168
        // It should load data before file being deleted
169 8
        $shp->loadFromFile('');
170
171
        // Delete the .dbf file extracted to 'TempDir'
172 8
        if ($tempDbfFile && isset($dbfFilePath) && @file_exists($dbfFilePath)) {
173 2
            unlink($dbfFilePath);
174
        }
175
176 8
        if ($shp->lastError != '') {
177
            Import::$hasError = true;
178
            Current::$message = Message::error(
179
                __('There was an error importing the ESRI shape file: "%s".'),
180
            );
181
            Current::$message->addParam($shp->lastError);
182
183
            return [];
184
        }
185
186 8
        switch ($shp->shapeType) {
187 8
            case ShapeType::Null:
188
                break;
189 8
            case ShapeType::Point:
190 4
                $gisType = 'point';
191 4
                break;
192 4
            case ShapeType::PolyLine:
193
                $gisType = 'multilinestring';
194
                break;
195 4
            case ShapeType::Polygon:
196 4
                $gisType = 'multipolygon';
197 4
                break;
198
            case ShapeType::MultiPoint:
199
                $gisType = 'multipoint';
200
                break;
201
            default:
202
                Import::$hasError = true;
203
                Current::$message = Message::error(
204
                    __('MySQL Spatial Extension does not support ESRI type "%s".'),
205
                );
206
                Current::$message->addParam($shp->getShapeName());
207
208
                return [];
209
        }
210
211 8
        if (isset($gisType)) {
212
            /** @var GisMultiLineString|GisMultiPoint|GisPoint|GisPolygon $gisObj */
213 8
            $gisObj = GisFactory::fromType($gisType);
214
        } else {
215
            $gisObj = null;
216
        }
217
218
        // If .dbf file is loaded, the number of extra data columns
219 8
        $numDataCols = $shp->getDBFHeader() !== null ? count($shp->getDBFHeader()) : 0;
220
221 8
        $rows = [];
222 8
        $colNames = [];
223 8
        foreach ($shp->records as $record) {
224 8
            $tempRow = [];
225 8
            if ($gisObj == null || ! method_exists($gisObj, 'getShape')) {
226
                $tempRow[] = null;
227
            } else {
228 8
                $tempRow[] = "GeomFromText('" . $gisObj->getShape($record->shpData) . "')";
229
            }
230
231 8
            if ($shp->getDBFHeader() !== null) {
232 8
                foreach ($shp->getDBFHeader() as $c) {
233 2
                    $cell = trim((string) $record->dbfData[$c[0]]);
234
235 2
                    if ($cell === '') {
236 1
                        $cell = 'NULL';
237
                    }
238
239 2
                    $tempRow[] = $cell;
240
                }
241
            }
242
243 8
            $rows[] = $tempRow;
244
        }
245
246 8
        if ($rows === []) {
247
            Import::$hasError = true;
248
            Current::$message = Message::error(
249
                __('The imported file does not contain any data!'),
250
            );
251
252
            return [];
253
        }
254
255
        // Column names for spatial column and the rest of the columns,
256
        // if they are available
257 8
        $colNames[] = 'SPATIAL';
258 8
        $dbfHeader = $shp->getDBFHeader();
259 8
        for ($n = 0; $n < $numDataCols; $n++) {
260 2
            if ($dbfHeader === null) {
261
                continue;
262
            }
263
264 2
            $colNames[] = $dbfHeader[$n][0];
265
        }
266
267
        // Set table name based on the number of tables
268 8
        if (Current::$database !== '') {
269
            $tableName = $this->import->getNextAvailableTableName(
270
                Current::$database,
271
                pathinfo(ImportSettings::$importFileName, PATHINFO_FILENAME),
0 ignored issues
show
Bug introduced by
It seems like pathinfo(PhpMyAdmin\Impo...ame, PATHINFO_FILENAME) can also be of type array; however, parameter $proposedTableName of PhpMyAdmin\Import\Import...extAvailableTableName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

271
                /** @scrutinizer ignore-type */ pathinfo(ImportSettings::$importFileName, PATHINFO_FILENAME),
Loading history...
272
            );
273
        } else {
274 8
            $tableName = 'TBL_NAME';
275
        }
276
277 8
        $table = new ImportTable($tableName, $colNames, $rows);
278
279
        // Use data from shape file to chose best-fit MySQL types for each column
280 8
        $analysis = $this->import->analyzeTable($table);
281
282
        // The first column is the SPATIAL column defined earlier
283
        // This column contains SQL functions and should not be formatted by buildSql()
284 8
        $analysis[0]->type = ColumnType::Geometry;
285 8
        $analysis[0]->isFullyFormattedSql = true;
286
287
        // Set database name to the currently selected one, if applicable
288 8
        $dbName = Current::$database !== '' ? Current::$database : 'SHP_DB';
289 8
        $createDb = Current::$database === '';
290
291
        // Created and execute necessary SQL statements from data
292 8
        $sqlStatements = [];
293 8
        if ($createDb) {
294 8
            $sqlStatements = $this->import->createDatabase($dbName, 'utf8', 'utf8_general_ci', []);
295
        }
296
297 8
        $this->import->buildSql($dbName, [$table], [$analysis], sqlData: $sqlStatements);
298
299 8
        ImportSettings::$finished = true;
300 8
        Import::$hasError = false;
301
302
        // Commit any possible data in buffers
303 8
        $this->import->runQuery('', $sqlStatements);
304
305 8
        return $sqlStatements;
306
    }
307
308
    /**
309
     * Returns specified number of bytes from the buffer.
310
     * Buffer automatically fetches next chunk of data when the buffer
311
     * falls short.
312
     * Sets $eof when ImportSettings::$finished is set and the buffer falls short.
313
     *
314
     * @param int $length number of bytes
315
     */
316 8
    public static function readFromBuffer(int $length): string
317
    {
318 8
        $import = new Import();
319
320 8
        if (strlen(self::$buffer) < $length) {
321 8
            if (ImportSettings::$finished) {
322 8
                self::$eof = true;
323
            } else {
324 8
                self::$buffer .= $import->getNextChunk(self::$importHandle);
325
            }
326
        }
327
328 8
        $result = substr(self::$buffer, 0, $length);
329 8
        self::$buffer = substr(self::$buffer, $length);
330
331 8
        return $result;
332
    }
333
}
334