Passed
Push — master ( f79a4b...8eafb5 )
by Thierry
02:11
created

Upload::getUploadDir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 6
rs 10
1
<?php
2
3
/**
4
 * Plugin.php - This class handles HTTP file upload.
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 count;
23
use function rtrim;
24
use function trim;
25
use function substr;
26
use function str_shuffle;
27
use function is_array;
28
use function mkdir;
29
use function is_readable;
30
use function is_writable;
31
use function json_encode;
32
use function json_decode;
33
use function file_exists;
34
use function file_get_contents;
35
use function file_put_contents;
36
use function move_uploaded_file;
37
use function unlink;
38
use function pathinfo;
39
use function random_bytes;
40
use function bin2hex;
41
use function call_user_func_array;
42
43
class Upload
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class Upload
Loading history...
44
{
45
    /**
46
     * @var Config
47
     */
48
    protected $xConfig;
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line(s) before first member var; 0 found
Loading history...
49
50
    /**
51
     * The request data validator
52
     *
53
     * @var Validator
54
     */
55
    protected $xValidator;
56
57
    /**
58
     * @var Translator
59
     */
60
    protected $xTranslator;
61
62
    /**
63
     * A user defined function to transform uploaded file names
64
     *
65
     * @var Closure
66
     */
67
    protected $cNameSanitizer = null;
68
69
    /**
70
     * The subdir where uploaded files are stored
71
     *
72
     * @var string
73
     */
74
    protected $sUploadSubdir = '';
75
76
    /**
77
     * The constructor
78
     *
79
     * @param Config $xConfig
0 ignored issues
show
Coding Style introduced by
Expected 5 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
80
     * @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...
81
     * @param Translator $xTranslator
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
82
     */
83
    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...
84
    {
85
        $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...
86
        $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...
87
        $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...
88
        $this->sUploadSubdir = $this->randomName() . DIRECTORY_SEPARATOR;
89
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
90
91
    /**
92
     * Generate a random name
93
     *
94
     * @return string
95
     */
96
    protected function randomName(): string
97
    {
98
        try
99
        {
100
            return bin2hex(random_bytes(7));
101
        }
102
        catch(Exception $e){}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
103
        // Generate the name
104
        $sChars = '0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz';
105
        return substr(str_shuffle($sChars), 0, 14);
106
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
107
108
    /**
109
     * Filter uploaded file name
110
     *
111
     * @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...
112
     *
113
     * @return void
114
     */
115
    public function setNameSanitizer(Closure $cNameSanitizer)
116
    {
117
        $this->cNameSanitizer = $cNameSanitizer;
118
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
119
120
    /**
121
     * Check uploaded files
122
     *
123
     * @param array $aFiles    The uploaded files
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
124
     *
125
     * @return void
126
     * @throws RequestException
127
     */
128
    private function checkFiles(array $aFiles)
129
    {
130
        foreach($aFiles as $sVarName => $aVarFiles)
131
        {
132
            foreach($aVarFiles as $aFile)
133
            {
134
                // Verify upload result
135
                if($aFile['error'] != 0)
136
                {
137
                    throw new RequestException($this->xTranslator->trans('errors.upload.failed', $aFile));
138
                }
139
                // Verify file validity (format, size)
140
                if(!$this->xValidator->validateUploadedFile($sVarName, $aFile))
141
                {
142
                    throw new RequestException($this->xValidator->getErrorMessage());
143
                }
144
            }
145
        }
146
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
147
148
    /**
149
     * Get a file from upload entry
150
     *
151
     * @param string $sVarName    The corresponding variable
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter name; 4 found
Loading history...
152
     * @param array $aVarFiles    An entry in the PHP $_FILES array
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
153
     * @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...
154
     *
155
     * @return null|array
156
     */
157
    private function getUploadedFile(string $sVarName, array $aVarFiles, int $nPosition): ?array
158
    {
159
        if(!$aVarFiles['name'][$nPosition])
160
        {
161
            return null;
162
        }
163
164
        // Filename without the extension
165
        $sFilename = pathinfo($aVarFiles['name'][$nPosition], PATHINFO_FILENAME);
166
        if(($this->cNameSanitizer))
167
        {
168
            $sFilename = (string)call_user_func_array($this->cNameSanitizer, [$sFilename, $sVarName]);
169
        }
170
171
        return [
172
            'name' => $aVarFiles['name'][$nPosition],
173
            'type' => $aVarFiles['type'][$nPosition],
174
            'tmp_name' => $aVarFiles['tmp_name'][$nPosition],
175
            'error' => $aVarFiles['error'][$nPosition],
176
            'size' => $aVarFiles['size'][$nPosition],
177
            'filename' => $sFilename,
178
            'extension' => pathinfo($aVarFiles['name'][$nPosition], PATHINFO_EXTENSION),
179
        ];
180
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
181
182
    /**
183
     * Read uploaded files info from HTTP request data
184
     *
185
     * @return array
186
     * @throws RequestException
187
     */
188
    public function getUploadedFiles(): array
189
    {
190
        // Check validity of the uploaded files
191
        $aUploadedFiles = [];
192
        foreach($_FILES as $sVarName => $aVarFiles)
193
        {
194
            // If there is only one file, transform each entry into an array,
195
            // so the same processing for multiple files can be applied.
196
            if(!is_array($aVarFiles['name']))
197
            {
198
                $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...
199
                $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...
200
                $aVarFiles['tmp_name'] = [$aVarFiles['tmp_name']];
201
                $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...
202
                $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...
203
            }
204
205
            $nFileCount = count($aVarFiles['name']);
206
            for($i = 0; $i < $nFileCount; $i++)
207
            {
208
                $aUploadedFile = $this->getUploadedFile($sVarName, $aVarFiles, $i);
209
                if(is_array($aUploadedFile))
210
                {
211
                    if(!isset($aUploadedFiles[$sVarName]))
212
                    {
213
                        $aUploadedFiles[$sVarName] = [];
214
                    }
215
                    $aUploadedFiles[$sVarName][] = $aUploadedFile;
216
                }
217
            }
218
        }
219
220
        // Check uploaded files validity
221
        $this->checkFiles($aUploadedFiles);
222
223
        return $aUploadedFiles;
224
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
225
226
    /**
227
     * Make sure the upload dir exists and is writable
228
     *
229
     * @param string $sUploadDir    The filename
230
     * @param string $sUploadSubDir    The filename
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
231
     *
232
     * @return string
233
     * @throws RequestException
234
     */
235
    private function _makeUploadDir(string $sUploadDir, string $sUploadSubDir): string
236
    {
237
        $sUploadDir = rtrim(trim($sUploadDir), '/\\') . DIRECTORY_SEPARATOR;
238
        // Verify that the upload dir exists and is writable
239
        if(!is_writable($sUploadDir))
240
        {
241
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
242
        }
243
        $sUploadDir .= $sUploadSubDir;
244
        if(!file_exists($sUploadDir) && !@mkdir($sUploadDir))
245
        {
246
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
247
        }
248
        return $sUploadDir;
249
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
250
251
    /**
252
     * Get the path to the upload dir
253
     *
254
     * @param string $sFieldId    The filename
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
255
     *
256
     * @return string
257
     * @throws RequestException
258
     */
259
    protected function getUploadDir(string $sFieldId): string
260
    {
261
        // Default upload dir
262
        $sDefaultUploadDir = $this->xConfig->getOption('upload.default.dir');
263
        $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...
264
        return $this->_makeUploadDir($sUploadDir, $this->sUploadSubdir);
0 ignored issues
show
Bug introduced by
It seems like $sUploadDir can also be of type null; however, parameter $sUploadDir of Jaxon\Request\Upload\Upload::_makeUploadDir() 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

264
        return $this->_makeUploadDir(/** @scrutinizer ignore-type */ $sUploadDir, $this->sUploadSubdir);
Loading history...
265
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
266
267
    /**
268
     * Get the path to the upload temp dir
269
     *
270
     * @return string
271
     * @throws RequestException
272
     */
273
    protected function getUploadTempDir(): string
274
    {
275
        // Default upload dir
276
        $sUploadDir = $this->xConfig->getOption('upload.default.dir');
277
        return $this->_makeUploadDir($sUploadDir, 'tmp' . DIRECTORY_SEPARATOR);
0 ignored issues
show
Bug introduced by
It seems like $sUploadDir can also be of type null; however, parameter $sUploadDir of Jaxon\Request\Upload\Upload::_makeUploadDir() 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

277
        return $this->_makeUploadDir(/** @scrutinizer ignore-type */ $sUploadDir, 'tmp' . DIRECTORY_SEPARATOR);
Loading history...
278
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
279
280
    /**
281
     * Get the path to the upload temp file
282
     *
283
     * @param string $sTempFile
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
284
     *
285
     * @return string
286
     * @throws RequestException
287
     */
288
    protected function getUploadTempFile(string $sTempFile): string
289
    {
290
        // Verify file name validity
291
        if(!$this->xValidator->validateTempFileName($sTempFile))
292
        {
293
            throw new RequestException($this->xTranslator->trans('errors.upload.invalid'));
294
        }
295
        $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...
296
        $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...
Bug introduced by
It seems like $sUploadDir can also be of type null; however, parameter $string of trim() 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

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

386
        /** @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...
387
        return $aUserFiles;
388
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 0 found
Loading history...
389
}
390