Passed
Push — master ( 5198fb...6eb156 )
by Thierry
02:34
created

UploadManager::checkFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 3
dl 0
loc 12
rs 10
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\Config\ConfigManager;
15
use Jaxon\Request\Validator;
16
use Jaxon\Utils\Translation\Translator;
17
use Jaxon\Exception\RequestException;
18
use Nyholm\Psr7\UploadedFile;
19
use Psr\Http\Message\ServerRequestInterface;
20
21
use Closure;
22
23
use function call_user_func;
24
use function file_exists;
25
use function file_get_contents;
26
use function file_put_contents;
27
use function is_array;
28
use function is_dir;
29
use function is_readable;
30
use function is_string;
31
use function is_writable;
32
use function json_decode;
33
use function json_encode;
34
use function mkdir;
35
use function realpath;
36
use function rtrim;
37
use function trim;
38
use function unlink;
39
40
class UploadManager
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class UploadManager
Loading history...
41
{
42
    /**
43
     * @var ConfigManager
44
     */
45
    protected $xConfigManager;
0 ignored issues
show
Coding Style introduced by
Expected 1 blank line(s) before first member var; 0 found
Loading history...
46
47
    /**
48
     * The request data validator
49
     *
50
     * @var Validator
51
     */
52
    protected $xValidator;
53
54
    /**
55
     * @var Translator
56
     */
57
    protected $xTranslator;
58
59
    /**
60
     * The file and dir name generator
61
     *
62
     * @var NameGeneratorInterface
63
     */
64
    protected $xNameGenerator;
65
66
    /**
67
     * The id of the upload field in the form
68
     *
69
     * @var string
70
     */
71
    protected $sUploadFieldId = '';
72
73
    /**
74
     * A user defined function to transform uploaded file names
75
     *
76
     * @var Closure
77
     */
78
    protected $cNameSanitizer = null;
79
80
    /**
81
     * A flat list of all uploaded files
82
     *
83
     * @var array
84
     */
85
    private $aAllFiles = [];
86
87
    /**
88
     * The constructor
89
     *
90
     * @param NameGeneratorInterface $xNameGenerator
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
91
     * @param ConfigManager $xConfigManager
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
92
     * @param Validator $xValidator
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 14 spaces after parameter type; 1 found
Loading history...
93
     * @param Translator $xTranslator
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 13 spaces after parameter type; 1 found
Loading history...
94
     */
95
    public function __construct(NameGeneratorInterface $xNameGenerator, ConfigManager $xConfigManager,
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines before function; 1 found
Loading history...
96
        Validator $xValidator, Translator $xTranslator)
97
    {
98
        $this->xNameGenerator = $xNameGenerator;
99
        $this->xConfigManager = $xConfigManager;
100
        $this->xValidator = $xValidator;
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...
101
        $this->xTranslator = $xTranslator;
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...
102
        // This feature is not yet implemented
103
        $this->setUploadFieldId('');
104
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
105
106
    /**
107
     * Generate a random name
108
     *
109
     * @return string
110
     */
111
    protected function randomName(): string
112
    {
113
        return $this->xNameGenerator->random(14);
114
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
115
116
    /**
117
     * Set the id of the upload field in the form
118
     *
119
     * @param string $sUploadFieldId
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
120
     *
121
     * @return void
122
     */
123
    public function setUploadFieldId(string $sUploadFieldId)
124
    {
125
        $this->sUploadFieldId = $sUploadFieldId;
126
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
127
128
    /**
129
     * Filter uploaded file name
130
     *
131
     * @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...
132
     *
133
     * @return void
134
     */
135
    public function setNameSanitizer(Closure $cNameSanitizer)
136
    {
137
        $this->cNameSanitizer = $cNameSanitizer;
138
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
139
140
    /**
141
     * Make sure the upload dir exists and is writable
142
     *
143
     * @param string $sUploadDir    The filename
144
     * @param string $sUploadSubDir    The filename
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
145
     *
146
     * @return string
147
     * @throws RequestException
148
     */
149
    private function _makeUploadDir(string $sUploadDir, string $sUploadSubDir): string
150
    {
151
        $sUploadDir = realpath(rtrim(trim($sUploadDir), '/\\'));
152
        // Verify that the upload dir exists and is writable
153
        if(!is_dir($sUploadDir) || !is_writable($sUploadDir))
154
        {
155
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
156
        }
157
        $sUploadDir .= DIRECTORY_SEPARATOR . $sUploadSubDir . DIRECTORY_SEPARATOR;
158
        if(!file_exists($sUploadDir) && !@mkdir($sUploadDir))
159
        {
160
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
161
        }
162
        return $sUploadDir;
163
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
164
165
    /**
166
     * Get the path to the upload dir
167
     *
168
     * @param string $sFieldId    The filename
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces after parameter name; 4 found
Loading history...
169
     *
170
     * @return string
171
     * @throws RequestException
172
     */
173
    protected function getUploadDir(string $sFieldId): string
174
    {
175
        // Default upload dir
176
        $sDefaultUploadDir = $this->xConfigManager->getOption('upload.default.dir');
177
        $sUploadDir = $this->xConfigManager->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...
178
        if(!is_string($sUploadDir))
179
        {
180
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
181
        }
182
        return $this->_makeUploadDir($sUploadDir, $this->randomName());
183
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
184
185
    /**
186
     * Get the path to the upload temp dir
187
     *
188
     * @return string
189
     * @throws RequestException
190
     */
191
    protected function getUploadTempDir(): string
192
    {
193
        // Default upload dir
194
        $sUploadDir = $this->xConfigManager->getOption('upload.default.dir');
195
        if(!is_string($sUploadDir))
196
        {
197
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
198
        }
199
        return $this->_makeUploadDir($sUploadDir, 'tmp');
200
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
201
202
    /**
203
     * Check uploaded files
204
     *
205
     * @param string $sVarName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 7 spaces after parameter type; 1 found
Loading history...
206
     * @param string $sUploadDir
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 7 spaces after parameter type; 1 found
Loading history...
207
     * @param UploadedFile $xHttpFile
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
208
     *
209
     * @return File
210
     * @throws RequestException
211
     */
212
    private function makeUploadedFile(string $sVarName, string $sUploadDir, UploadedFile $xHttpFile): File
213
    {
214
        // Filename without the extension. Needs to be sanitized.
215
        $sName = pathinfo($xHttpFile->getClientFilename(), PATHINFO_FILENAME);
216
        if($this->cNameSanitizer !== null)
217
        {
218
            $sName = (string)call_user_func($this->cNameSanitizer, $sName, $sVarName, $this->sUploadFieldId);
219
        }
220
        // Check the uploaded file validity
221
        if($xHttpFile->getError())
222
        {
223
            throw new RequestException($this->xTranslator->trans('errors.upload.failed', ['name' => $sVarName]));
224
        }
225
        // Set the user file data
226
        $xFile = File::fromHttpFile($sName, $sUploadDir, $xHttpFile);
0 ignored issues
show
Bug introduced by
It seems like $sName can also be of type array; however, parameter $sName of Jaxon\Request\Upload\File::fromHttpFile() 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

226
        $xFile = File::fromHttpFile(/** @scrutinizer ignore-type */ $sName, $sUploadDir, $xHttpFile);
Loading history...
227
        // Verify file validity (format, size)
228
        if(!$this->xValidator->validateUploadedFile($sVarName, $xFile))
229
        {
230
            throw new RequestException($this->xValidator->getErrorMessage());
231
        }
232
        // All's right, save the file for copy.
233
        $this->aAllFiles[] = ['temp' => $xHttpFile, 'user' => $xFile];
234
        return $xFile;
235
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
236
237
    /**
238
     * Read uploaded files info from HTTP request data
239
     *
240
     * @param ServerRequestInterface $xRequest
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
241
     *
242
     * @return array
243
     * @throws RequestException
244
     */
245
    public function readFromHttpData(ServerRequestInterface $xRequest): array
246
    {
247
        // Get the uploaded files
248
        $aTempFiles = $xRequest->getUploadedFiles();
249
250
        $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...
251
        $this->aAllFiles = []; // A flat list of all uploaded files
252
        foreach($aTempFiles as $sVarName => $aFiles)
253
        {
254
            $aUserFiles[$sVarName] = [];
255
            // Get the path to the upload dir
256
            $sUploadDir = $this->getUploadDir($sVarName);
257
            if(!is_array($aFiles))
258
            {
259
                $aFiles = [$aFiles];
260
            }
261
            foreach($aFiles as $xHttpFile)
262
            {
263
                $aUserFiles[$sVarName][] = $this->makeUploadedFile($sVarName, $sUploadDir, $xHttpFile);
264
            }
265
        }
266
        // Copy the uploaded files from the temp dir to the user dir
267
        foreach($this->aAllFiles as $aFiles)
268
        {
269
            $aFiles['temp']->moveTo($aFiles['user']->path());
270
        }
271
        return $aUserFiles;
272
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
273
274
    /**
275
     * Save uploaded files info to a temp file
276
     *
277
     * @param array $aUserFiles
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
278
     *
279
     * @return string
280
     * @throws RequestException
281
     */
282
    public function saveToTempFile(array $aUserFiles): string
283
    {
284
        // Convert uploaded file to an array
285
        $aFiles = [];
286
        foreach($aUserFiles as $sVarName => $aVarFiles)
287
        {
288
            $aFiles[$sVarName] = [];
289
            foreach($aVarFiles as $aVarFile)
290
            {
291
                $aFiles[$sVarName][] = $aVarFile->toTempData();
292
            }
293
        }
294
        // Save upload data in a temp file
295
        $sUploadDir = $this->getUploadTempDir();
296
        $sTempFile = $this->randomName();
297
        file_put_contents($sUploadDir . $sTempFile . '.json', json_encode($aFiles));
298
        return $sTempFile;
299
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
300
301
    /**
302
     * Get the path to the upload temp file
303
     *
304
     * @param string $sTempFile
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
305
     *
306
     * @return string
307
     * @throws RequestException
308
     */
309
    protected function getUploadTempFile(string $sTempFile): string
310
    {
311
        // Verify file name validity
312
        if(!$this->xValidator->validateTempFileName($sTempFile))
313
        {
314
            throw new RequestException($this->xTranslator->trans('errors.upload.invalid'));
315
        }
316
        $sUploadDir = $this->xConfigManager->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...
317
        $sUploadDir = realpath(rtrim(trim($sUploadDir), '/\\')) . DIRECTORY_SEPARATOR . 'tmp' . 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...
318
        $sUploadTempFile = $sUploadDir . $sTempFile . '.json';
319
        if(!is_readable($sUploadTempFile))
320
        {
321
            throw new RequestException($this->xTranslator->trans('errors.upload.access'));
322
        }
323
        return $sUploadTempFile;
324
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 1 found
Loading history...
325
326
    /**
327
     * Read uploaded files info from a temp file
328
     *
329
     * @param string $sTempFile
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
330
     *
331
     * @return array
332
     * @throws RequestException
333
     */
334
    public function readFromTempFile(string $sTempFile): array
335
    {
336
        // Upload temp file
337
        $sUploadTempFile = $this->getUploadTempFile($sTempFile);
338
        $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...
339
        $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...
340
        foreach($aFiles as $sVarName => $aVarFiles)
341
        {
342
            $aUserFiles[$sVarName] = [];
343
            foreach($aVarFiles as $aVarFile)
344
            {
345
                $aUserFiles[$sVarName][] = File::fromTempFile($aVarFile);
346
            }
347
        }
348
        @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

348
        /** @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...
349
        return $aUserFiles;
350
    }
0 ignored issues
show
Coding Style introduced by
Expected 2 blank lines after function; 0 found
Loading history...
351
}
352