Completed
Pull Request — master (#39)
by Thierry
03:03
created

FileUpload::readFromHttpData()   C

Complexity

Conditions 14
Paths 55

Size

Total Lines 93

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
nc 55
nop 0
dl 0
loc 93
rs 5.166
c 0
b 0
f 0

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
/**
4
 * FileUpload.php - This class implements file upload with Ajax.
5
 *
6
 * @package jaxon-core
7
 * @author Thierry Feuzeu <[email protected]>
8
 * @copyright 2017 Thierry Feuzeu <[email protected]>
9
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
10
 * @link https://github.com/jaxon-php/jaxon-core
11
 */
12
13
namespace Jaxon\Request\Plugin;
14
15
use Jaxon\Jaxon;
16
use Jaxon\Plugin\Request as RequestPlugin;
17
use Jaxon\Request\Support\UploadedFile;
18
19
use Closure;
20
21
class FileUpload extends RequestPlugin
22
{
23
    use \Jaxon\Utils\Traits\Validator;
24
    use \Jaxon\Utils\Traits\Translator;
25
26
    /**
27
     * The uploaded files copied in the user dir
28
     *
29
     * @var array
30
     */
31
    protected $aUserFiles;
32
33
    /**
34
     * The name of file containing upload data
35
     *
36
     * @var string
37
     */
38
    protected $sTempFile = '';
39
40
    /**
41
     * The subdir where uploaded files are stored
42
     *
43
     * @var string
44
     */
45
    protected $sUploadSubdir = '';
46
47
    /**
48
     * A user defined function to transform uploaded file names
49
     *
50
     * @var Closure
51
     */
52
    protected $fFileFilter = null;
53
54
    /**
55
     * Read uploaded files info from the $_FILES global var
56
     */
57
    public function __construct()
58
    {
59
        $this->aUserFiles = [];
60
        $this->sUploadSubdir = uniqid() . DIRECTORY_SEPARATOR;
61
62
        if(array_key_exists('jxnupl', $_POST))
63
        {
64
            $this->sTempFile = $_POST['jxnupl'];
65
        }
66
        elseif(array_key_exists('jxnupl', $_GET))
67
        {
68
            $this->sTempFile = $_GET['jxnupl'];
69
        }
70
    }
71
72
    /**
73
     * Filter uploaded file name
74
     *
75
     * @param Closure       $fFileFilter            The closure which filters filenames
76
     *
77
     * @return void
78
     */
79
    public function setFileFilter(Closure $fFileFilter)
80
    {
81
        $this->fFileFilter = $fFileFilter;
82
    }
83
84
    /**
85
     * Filter uploaded file name
86
     *
87
     * @param string        $sFilename              The filename
88
     * @param string        $sVarName               The associated variable name
89
     *
90
     * @return string
91
     */
92
    protected function filterFilename($sFilename, $sVarName)
93
    {
94
        if(($this->fFileFilter))
95
        {
96
            $fFileFilter = $this->fFileFilter;
97
            $sFilename = (string)$fFileFilter($sFilename, $sVarName);
98
        }
99
        return $sFilename;
100
    }
101
102
    /**
103
     * Get the path to the upload dir
104
     *
105
     * @param string        $sFieldId               The filename
106
     *
107
     * @return string
108
     */
109
    protected function getUploadDir($sFieldId)
110
    {
111
        // Default upload dir
112
        $sDefaultUploadDir = $this->getOption('upload.default.dir');
113
        $sUploadDir = $this->getOption('upload.files.' . $sFieldId . '.dir', $sDefaultUploadDir);
114
        $sUploadDir = rtrim(trim($sUploadDir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
115
        // Verify that the upload dir exists and is writable
116
        if(!is_writable($sUploadDir))
117
        {
118
            throw new \Jaxon\Exception\Error($this->trans('errors.upload.access'));
119
        }
120
        $sUploadDir .= $this->sUploadSubdir;
121
        @mkdir($sUploadDir);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
122
        return $sUploadDir;
123
    }
124
125
    /**
126
     * Get the path to the upload temp dir
127
     *
128
     * @return string
129
     */
130
    protected function getUploadTempDir()
131
    {
132
        // Default upload dir
133
        $sUploadDir = $this->getOption('upload.default.dir');
134
        $sUploadDir = rtrim(trim($sUploadDir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
135
        // Verify that the upload dir exists and is writable
136
        if(!is_writable($sUploadDir))
137
        {
138
            throw new \Jaxon\Exception\Error($this->trans('errors.upload.access'));
139
        }
140
        $sUploadDir .= 'tmp' . DIRECTORY_SEPARATOR;
141
        @mkdir($sUploadDir);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
142
        return $sUploadDir;
143
    }
144
145
    /**
146
     * Get the path to the upload temp file
147
     *
148
     * @return string
149
     */
150
    protected function getUploadTempFile()
151
    {
152
        $sUploadDir = $this->getOption('upload.default.dir');
153
        $sUploadDir = rtrim(trim($sUploadDir), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
154
        $sUploadDir .= 'tmp' . DIRECTORY_SEPARATOR;
155
        $sUploadTempFile = $sUploadDir . $this->sTempFile . '.json';
156
        if(!is_readable($sUploadTempFile))
157
        {
158
            throw new \Jaxon\Exception\Error($this->trans('errors.upload.access'));
159
        }
160
        return $sUploadTempFile;
161
    }
162
163
    /**
164
     * Read uploaded files info from HTTP request data
165
     *
166
     * @return void
167
     */
168
    protected function readFromHttpData()
169
    {
170
        // Check validity of the uploaded files
171
        $aTempFiles = [];
172
        foreach($_FILES as $sVarName => $aFile)
173
        {
174
            if(is_array($aFile['name']))
175
            {
176
                for($i = 0; $i < count($aFile['name']); $i++)
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
177
                {
178
                    if(!$aFile['name'][$i])
179
                    {
180
                        continue;
181
                    }
182
                    if(!array_key_exists($sVarName, $aTempFiles))
183
                    {
184
                        $aTempFiles[$sVarName] = [];
185
                    }
186
                    // Filename without the extension
187
                    $sFilename = $this->filterFilename(pathinfo($aFile['name'][$i], PATHINFO_FILENAME), $sVarName);
188
                    // Copy the file data into the local array
189
                    $aTempFiles[$sVarName][] = [
190
                        'name' => $aFile['name'][$i],
191
                        'type' => $aFile['type'][$i],
192
                        'tmp_name' => $aFile['tmp_name'][$i],
193
                        'error' => $aFile['error'][$i],
194
                        'size' => $aFile['size'][$i],
195
                        'filename' => $sFilename,
196
                        'extension' => pathinfo($aFile['name'][$i], PATHINFO_EXTENSION),
197
                    ];
198
                }
199
            }
200
            else
201
            {
202
                if(!$aFile['name'])
203
                {
204
                    continue;
205
                }
206
                if(!array_key_exists($sVarName, $aTempFiles))
207
                {
208
                    $aTempFiles[$sVarName] = [];
209
                }
210
                // Filename without the extension
211
                $sFilename = $this->filterFilename(pathinfo($aFile['name'], PATHINFO_FILENAME), $sVarName);
212
                // Copy the file data into the local array
213
                $aTempFiles[$sVarName][] = [
214
                    'name' => $aFile['name'],
215
                    'type' => $aFile['type'],
216
                    'tmp_name' => $aFile['tmp_name'],
217
                    'error' => $aFile['error'],
218
                    'size' => $aFile['size'],
219
                    'filename' => $sFilename,
220
                    'extension' => pathinfo($aFile['name'], PATHINFO_EXTENSION),
221
                ];
222
            }
223
        }
224
225
        // Check uploaded files validity
226
        foreach($aTempFiles as $sVarName => $aFiles)
227
        {
228
            foreach($aFiles as $aFile)
229
            {
230
                // Verify upload result
231
                if($aFile['error'] != 0)
232
                {
233
                    throw new \Jaxon\Exception\Error($this->trans('errors.upload.failed', $aFile));
234
                }
235
                // Verify file validity (format, size)
236
                if(!$this->validateUploadedFile($sVarName, $aFile))
237
                {
238
                    throw new \Jaxon\Exception\Error($this->getValidatorMessage());
239
                }
240
                // Get the path to the upload dir
241
                $sUploadDir = $this->getUploadDir($sVarName);
0 ignored issues
show
Unused Code introduced by
$sUploadDir is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
242
            }
243
        }
244
245
        // Copy the uploaded files from the temp dir to the user dir
246
        foreach($aTempFiles as $sVarName => $aTempFiles)
247
        {
248
            $this->aUserFiles[$sVarName] = [];
249
            foreach($aTempFiles as $aFile)
250
            {
251
                // Get the path to the upload dir
252
                $sUploadDir = $this->getUploadDir($sVarName);
253
                // Set the user file data
254
                $xUploadedFile = UploadedFile::fromHttpData($sUploadDir, $aFile);
255
                // All's right, move the file to the user dir.
256
                move_uploaded_file($aFile["tmp_name"], $xUploadedFile->path());
257
                $this->aUserFiles[$sVarName][] = $xUploadedFile;
258
            }
259
        }
260
    }
261
262
    /**
263
     * Save uploaded files info to a temp file
264
     *
265
     * @return void
266
     */
267
    protected function saveToTempFile()
268
    {
269
        // Convert uploaded file to an array
270
        $aFiles = [];
271
        foreach($this->aUserFiles as $sVarName => $aUserFiles)
272
        {
273
            $aFiles[$sVarName] = [];
274
            foreach($aUserFiles as $aUserFile)
275
            {
276
                 $aFiles[$sVarName][] = $aUserFile->toTempData();
277
            }
278
        }
279
        // Save upload data in a temp file
280
        $sUploadDir = $this->getUploadTempDir();
281
        $this->sTempFile = uniqid();
282
        file_put_contents($sUploadDir . $this->sTempFile . '.json', json_encode($aFiles));
283
    }
284
285
    /**
286
     * Read uploaded files info from a temp file
287
     *
288
     * @return void
289
     */
290
    protected function readFromTempFile()
291
    {
292
        // Upload temp file
293
        $sUploadTempFile = $this->getUploadTempFile();
294
        $aFiles = json_decode(file_get_contents($sUploadTempFile), true);
295
        foreach($aFiles as $sVarName => $aUserFiles)
296
        {
297
            $this->aUserFiles[$sVarName] = [];
298
            foreach($aUserFiles as $aUserFile)
299
            {
300
                $this->aUserFiles[$sVarName][] = UploadedFile::fromTempData($aUserFile);
301
            }
302
        }
303
        unlink($sUploadTempFile);
304
    }
305
306
    /**
307
     * Return the name of this plugin
308
     *
309
     * @return string
310
     */
311
    public function getName()
312
    {
313
        return Jaxon::FILE_UPLOAD;
314
    }
315
316
    /**
317
     * Get the uploaded files
318
     *
319
     * @return array
320
     */
321
    public function getUploadedFiles()
322
    {
323
        return $this->aUserFiles;
324
    }
325
326
    /**
327
     * Register a browser event
328
     *
329
     * @param array         $aArgs                An array containing the event specification
330
     *
331
     * @return \Jaxon\Request\Request
332
     */
333
    public function register($aArgs)
334
    {
335
        return false;
336
    }
337
338
    /**
339
     * Generate a hash for the registered browser events
340
     *
341
     * @return string
342
     */
343
    public function generateHash()
344
    {
345
        return '';
346
    }
347
348
    /**
349
     * Generate client side javascript code for the registered browser events
350
     *
351
     * @return string
352
     */
353
    public function getScript()
354
    {
355
        return '';
356
    }
357
358
    /**
359
     * Check if this plugin can process the incoming Jaxon request
360
     *
361
     * @return boolean
362
     */
363
    public function canProcessRequest()
364
    {
365
        return (count($_FILES) > 0 || ($this->sTempFile));
366
    }
367
368
    /**
369
     * Process the uploaded files into the HTTP request
370
     *
371
     * @return boolean
372
     */
373
    public function processRequest()
374
    {
375
        if(!$this->canProcessRequest())
376
        {
377
            return false;
378
        }
379
        if(count($_FILES) > 0)
380
        {
381
            $this->readFromHttpData();
382
        }
383
        elseif(($this->sTempFile))
384
        {
385
            $this->readFromTempFile();
386
        }
387
        return true;
388
    }
389
390
    /**
391
     * Check uploaded files validity and move them to the user dir
392
     *
393
     * @return boolean
394
     */
395
    public function saveUploadedFiles()
396
    {
397
        // Process uploaded files
398
        if(!$this->processRequest())
399
        {
400
            return '';
401
        }
402
        // Save upload data in a temp file
403
        $this->saveToTempFile();
404
        return $this->sTempFile;
405
    }
406
}
407