Passed
Push — master ( a07882...2b8759 )
by Thierry
02:21
created

UploadManager::getUploadedFiles()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 36
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 17
nc 9
nop 0
dl 0
loc 36
rs 9.0777
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * UploadManager.php - This class processes uploaded files.
5
 *
6
 * @package jaxon-core
0 ignored issues
show
Coding Style introduced by
Package name "jaxon-core" is not valid; consider "Jaxoncore" instead
Loading history...
7
 * @author Thierry Feuzeu <[email protected]>
8
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
9
 * @link https://github.com/jaxon-php/jaxon-core
10
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
PHP version not specified
Loading history...
11
12
namespace Jaxon\Request\Upload;
13
14
use Jaxon\Request\Validator;
15
use Jaxon\Utils\Config\Config;
16
use Jaxon\Utils\Translation\Translator;
17
use Jaxon\Exception\RequestException;
18
19
use Closure;
20
use Exception;
21
22
use function bin2hex;
23
use function call_user_func_array;
24
use function count;
25
use function file_exists;
26
use function file_get_contents;
27
use function file_put_contents;
28
use function is_array;
29
use function is_dir;
30
use function is_readable;
31
use function is_string;
32
use function is_writable;
33
use function json_decode;
34
use function json_encode;
35
use function mkdir;
36
use function move_uploaded_file;
37
use function pathinfo;
38
use function random_bytes;
39
use function rtrim;
40
use function str_shuffle;
41
use function substr;
42
use function trim;
43
use function unlink;
44
45
class UploadManager
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class UploadManager
Loading history...
46
{
47
    /**
48
     * @var Config
49
     */
50
    protected $xConfig;
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line(s) before first member var; 0 found
Loading history...
51
52
    /**
53
     * The request data validator
54
     *
55
     * @var Validator
56
     */
57
    protected $xValidator;
58
59
    /**
60
     * @var Translator
61
     */
62
    protected $xTranslator;
63
64
    /**
65
     * A user defined function to transform uploaded file names
66
     *
67
     * @var Closure
68
     */
69
    protected $cNameSanitizer = null;
70
71
    /**
72
     * The subdir where uploaded files are stored
73
     *
74
     * @var string
75
     */
76
    protected $sUploadSubdir = '';
77
78
    /**
79
     * The constructor
80
     *
81
     * @param Config $xConfig
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 5 spaces after parameter type; 1 found
Loading history...
82
     * @param Validator $xValidator
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
83
     * @param Translator $xTranslator
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
84
     */
85
    public function __construct(Config $xConfig, Validator $xValidator, Translator $xTranslator)
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines before function; 1 found
Loading history...
86
    {
87
        $this->xConfig = $xConfig;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 7 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
88
        $this->xValidator = $xValidator;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
89
        $this->xTranslator = $xTranslator;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
90
        $this->sUploadSubdir = $this->randomName() . DIRECTORY_SEPARATOR;
91
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
92
93
    /**
94
     * Generate a random name
95
     *
96
     * @return string
97
     */
98
    protected function randomName(): string
99
    {
100
        try
101
        {
102
            return bin2hex(random_bytes(7));
103
        }
104
        catch(Exception $e){}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
105
        // Generate the name
106
        $sChars = '0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz';
107
        return substr(str_shuffle($sChars), 0, 14);
108
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
109
110
    /**
111
     * Filter uploaded file name
112
     *
113
     * @param Closure $cNameSanitizer    The closure which filters filenames
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
114
     *
115
     * @return void
116
     */
117
    public function setNameSanitizer(Closure $cNameSanitizer)
118
    {
119
        $this->cNameSanitizer = $cNameSanitizer;
120
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
121
122
    /**
123
     * Check uploaded files
124
     *
125
     * @param array $aFiles    The uploaded files
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
126
     *
127
     * @return void
128
     * @throws RequestException
129
     */
130
    private function checkFiles(array $aFiles)
131
    {
132
        foreach($aFiles as $sVarName => $aVarFiles)
133
        {
134
            foreach($aVarFiles as $aFile)
135
            {
136
                // Verify upload result
137
                if($aFile['error'] != 0)
138
                {
139
                    throw new RequestException($this->xTranslator->trans('errors.upload.failed', $aFile));
140
                }
141
                // Verify file validity (format, size)
142
                if(!$this->xValidator->validateUploadedFile($sVarName, $aFile))
143
                {
144
                    throw new RequestException($this->xValidator->getErrorMessage());
145
                }
146
            }
147
        }
148
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
149
150
    /**
151
     * Get a file from upload entry
152
     *
153
     * @param string $sVarName    The corresponding variable
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter name; 4 found
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
154
     * @param array $aVarFiles    An entry in the PHP $_FILES array
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
155
     * @param integer $nPosition    The position of the file to be processed
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
156
     *
157
     * @return null|array
158
     */
159
    private function getUploadedFile(string $sVarName, array $aVarFiles, int $nPosition): ?array
160
    {
161
        if(!$aVarFiles['name'][$nPosition])
162
        {
163
            return null;
164
        }
165
166
        // Filename without the extension
167
        $sFilename = pathinfo($aVarFiles['name'][$nPosition], PATHINFO_FILENAME);
168
        if(($this->cNameSanitizer))
169
        {
170
            $sFilename = (string)call_user_func_array($this->cNameSanitizer, [$sFilename, $sVarName]);
171
        }
172
173
        return [
174
            'name' => $aVarFiles['name'][$nPosition],
175
            'type' => $aVarFiles['type'][$nPosition],
176
            'tmp_name' => $aVarFiles['tmp_name'][$nPosition],
177
            'error' => $aVarFiles['error'][$nPosition],
178
            'size' => $aVarFiles['size'][$nPosition],
179
            'filename' => $sFilename,
180
            'extension' => pathinfo($aVarFiles['name'][$nPosition], PATHINFO_EXTENSION),
181
        ];
182
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
183
184
    /**
185
     * Read uploaded files info from HTTP request data
186
     *
187
     * @return array
188
     * @throws RequestException
189
     */
190
    public function getUploadedFiles(): array
191
    {
192
        // Check validity of the uploaded files
193
        $aUploadedFiles = [];
194
        foreach($_FILES as $sVarName => $aVarFiles)
195
        {
196
            // If there is only one file, transform each entry into an array,
197
            // so the same processing for multiple files can be applied.
198
            if(!is_array($aVarFiles['name']))
199
            {
200
                $aVarFiles['name'] = [$aVarFiles['name']];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
201
                $aVarFiles['type'] = [$aVarFiles['type']];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
202
                $aVarFiles['tmp_name'] = [$aVarFiles['tmp_name']];
203
                $aVarFiles['error'] = [$aVarFiles['error']];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
204
                $aVarFiles['size'] = [$aVarFiles['size']];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
205
            }
206
207
            $nFileCount = count($aVarFiles['name']);
208
            for($i = 0; $i < $nFileCount; $i++)
209
            {
210
                $aUploadedFile = $this->getUploadedFile($sVarName, $aVarFiles, $i);
211
                if(is_array($aUploadedFile))
212
                {
213
                    if(!isset($aUploadedFiles[$sVarName]))
214
                    {
215
                        $aUploadedFiles[$sVarName] = [];
216
                    }
217
                    $aUploadedFiles[$sVarName][] = $aUploadedFile;
218
                }
219
            }
220
        }
221
222
        // Check uploaded files validity
223
        $this->checkFiles($aUploadedFiles);
224
225
        return $aUploadedFiles;
226
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
227
228
    /**
229
     * Make sure the upload dir exists and is writable
230
     *
231
     * @param string $sUploadDir    The filename
232
     * @param string $sUploadSubDir    The filename
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
233
     *
234
     * @return string
235
     * @throws RequestException
236
     */
237
    private function _makeUploadDir(string $sUploadDir, string $sUploadSubDir): string
238
    {
239
        $sUploadDir = rtrim(trim($sUploadDir), '/\\') . DIRECTORY_SEPARATOR;
240
        // Verify that the upload dir exists and is writable
241
        if(!is_writable($sUploadDir))
242
        {
243
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
244
        }
245
        $sUploadDir .= $sUploadSubDir;
246
        if(!file_exists($sUploadDir) && !@mkdir($sUploadDir))
247
        {
248
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
249
        }
250
        return $sUploadDir;
251
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
252
253
    /**
254
     * Get the path to the upload dir
255
     *
256
     * @param string $sFieldId    The filename
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
257
     *
258
     * @return string
259
     * @throws RequestException
260
     */
261
    protected function getUploadDir(string $sFieldId): string
262
    {
263
        // Default upload dir
264
        $sDefaultUploadDir = $this->xConfig->getOption('upload.default.dir');
265
        $sUploadDir = $this->xConfig->getOption('upload.files.' . $sFieldId . '.dir', $sDefaultUploadDir);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 8 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
266
        if(!is_string($sUploadDir) || !is_dir($sUploadDir))
267
        {
268
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
269
        }
270
        return $this->_makeUploadDir($sUploadDir, $this->sUploadSubdir);
271
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
272
273
    /**
274
     * Get the path to the upload temp dir
275
     *
276
     * @return string
277
     * @throws RequestException
278
     */
279
    protected function getUploadTempDir(): string
280
    {
281
        // Default upload dir
282
        $sUploadDir = $this->xConfig->getOption('upload.default.dir');
283
        if(!is_string($sUploadDir) || !is_dir($sUploadDir))
284
        {
285
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
286
        }
287
        return $this->_makeUploadDir($sUploadDir, 'tmp' . DIRECTORY_SEPARATOR);
288
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
289
290
    /**
291
     * Get the path to the upload temp file
292
     *
293
     * @param string $sTempFile
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
294
     *
295
     * @return string
296
     * @throws RequestException
297
     */
298
    protected function getUploadTempFile(string $sTempFile): string
299
    {
300
        // Verify file name validity
301
        if(!$this->xValidator->validateTempFileName($sTempFile))
302
        {
303
            throw new RequestException($this->xTranslator->trans('errors.upload.invalid'));
304
        }
305
        $sUploadDir = $this->xConfig->getOption('upload.default.dir', '');
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
306
        $sUploadDir = rtrim(trim($sUploadDir), '/\\') . DIRECTORY_SEPARATOR;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
307
        $sUploadDir .= 'tmp' . DIRECTORY_SEPARATOR;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 5 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
308
        $sUploadTempFile = $sUploadDir . $sTempFile . '.json';
309
        if(!is_readable($sUploadTempFile))
310
        {
311
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
312
        }
313
        return $sUploadTempFile;
314
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
315
316
    /**
317
     * Read uploaded files info from HTTP request data
318
     *
319
     * @return array
320
     * @throws RequestException
321
     */
322
    public function readFromHttpData(): array
323
    {
324
        // Check validity of the uploaded files
325
        $aTempFiles = $this->getUploadedFiles();
326
327
        // Copy the uploaded files from the temp dir to the user dir
328
        $aUserFiles = [];
329
        foreach($aTempFiles as $sVarName => $aFiles)
330
        {
331
            $aUserFiles[$sVarName] = [];
332
            // Get the path to the upload dir
333
            $sUploadDir = $this->getUploadDir($sVarName);
334
335
            foreach($aFiles as $aFile)
336
            {
337
                // Set the user file data
338
                $xUploadedFile = File::fromHttpData($sUploadDir, $aFile);
339
                // All's right, move the file to the user dir.
340
                move_uploaded_file($aFile["tmp_name"], $xUploadedFile->path());
341
                $aUserFiles[$sVarName][] = $xUploadedFile;
342
            }
343
        }
344
        return $aUserFiles;
345
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
346
347
    /**
348
     * Save uploaded files info to a temp file
349
     *
350
     * @param array $aUserFiles
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
351
     *
352
     * @return string
353
     * @throws RequestException
354
     */
355
    public function saveToTempFile(array $aUserFiles): string
356
    {
357
        // Convert uploaded file to an array
358
        $aFiles = [];
359
        foreach($aUserFiles as $sVarName => $aVarFiles)
360
        {
361
            $aFiles[$sVarName] = [];
362
            foreach($aVarFiles as $aVarFile)
363
            {
364
                $aFiles[$sVarName][] = $aVarFile->toTempData();
365
            }
366
        }
367
        // Save upload data in a temp file
368
        $sUploadDir = $this->getUploadTempDir();
369
        $sTempFile = $this->randomName();
370
        file_put_contents($sUploadDir . $sTempFile . '.json', json_encode($aFiles));
371
        return $sTempFile;
372
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
373
374
    /**
375
     * Read uploaded files info from a temp file
376
     *
377
     * @param string $sTempFile
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
378
     *
379
     * @return array
380
     * @throws RequestException
381
     */
382
    public function readFromTempFile(string $sTempFile): array
383
    {
384
        // Upload temp file
385
        $sUploadTempFile = $this->getUploadTempFile($sTempFile);
386
        $aFiles = json_decode(file_get_contents($sUploadTempFile), true);
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 10 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
387
        $aUserFiles = [];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 6 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
388
        foreach($aFiles as $sVarName => $aVarFiles)
389
        {
390
            $aUserFiles[$sVarName] = [];
391
            foreach($aVarFiles as $aVarFile)
392
            {
393
                $aUserFiles[$sVarName][] = File::fromTempData($aVarFile);
394
            }
395
        }
396
        @unlink($sUploadTempFile);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

396
        /** @scrutinizer ignore-unhandled */ @unlink($sUploadTempFile);

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...
397
        return $aUserFiles;
398
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 0 found
Loading history...
399
}
400