Completed
Push — master ( f42a52...0a9499 )
by Thierry
02:50 queued 01:16
created

FileUpload::getUploadTempFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
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
use Jaxon\Response\UploadResponse;
19
20
use Exception;
21
use Closure;
22
23
class FileUpload extends RequestPlugin
24
{
25
    use \Jaxon\Features\Config;
26
    use \Jaxon\Features\Validator;
27
    use \Jaxon\Features\Translator;
28
29
    /**
30
     * The uploaded files copied in the user dir
31
     *
32
     * @var array
33
     */
34
    protected $aUserFiles = [];
35
36
    /**
37
     * The name of file containing upload data
38
     *
39
     * @var string
40
     */
41
    protected $sTempFile = '';
42
43
    /**
44
     * The subdir where uploaded files are stored
45
     *
46
     * @var string
47
     */
48
    protected $sUploadSubdir = '';
49
50
    /**
51
     * A user defined function to transform uploaded file names
52
     *
53
     * @var Closure
54
     */
55
    protected $cFileFilter = null;
56
57
    /**
58
     * Is the current request an HTTP upload
59
     *
60
     * @var boolean
61
     */
62
    protected $bRequestIsHttpUpload = false;
63
64
    /**
65
     * Read uploaded files info from the $_FILES global var
66
     */
67
    public function __construct()
68
    {
69
        $this->sUploadSubdir = uniqid() . DIRECTORY_SEPARATOR;
70
71
        if(array_key_exists('jxnupl', $_POST))
72
        {
73
            $this->sTempFile = $_POST['jxnupl'];
74
        }
75
        elseif(array_key_exists('jxnupl', $_GET))
76
        {
77
            $this->sTempFile = $_GET['jxnupl'];
78
        }
79
    }
80
81
    /**
82
     * Filter uploaded file name
83
     *
84
     * @param Closure       $cFileFilter            The closure which filters filenames
85
     *
86
     * @return void
87
     */
88
    public function setFileFilter(Closure $cFileFilter)
89
    {
90
        $this->cFileFilter = $cFileFilter;
91
    }
92
93
    /**
94
     * Filter uploaded file name
95
     *
96
     * @param string        $sFilename              The filename
97
     * @param string        $sVarName               The associated variable name
98
     *
99
     * @return string
100
     */
101
    protected function filterFilename($sFilename, $sVarName)
102
    {
103
        if(($this->cFileFilter))
104
        {
105
            $cFileFilter = $this->cFileFilter;
106
            $sFilename = (string)$cFileFilter($sFilename, $sVarName);
107
        }
108
        return $sFilename;
109
    }
110
111
    /**
112
     * Get the path to the upload dir
113
     *
114
     * @param string        $sFieldId               The filename
115
     *
116
     * @return string
117
     */
118
    protected function getUploadDir($sFieldId)
119
    {
120
        // Default upload dir
121
        $sDefaultUploadDir = $this->getOption('upload.default.dir');
122
        $sUploadDir = $this->getOption('upload.files.' . $sFieldId . '.dir', $sDefaultUploadDir);
123
        $sUploadDir = rtrim(trim($sUploadDir), '/\\') . DIRECTORY_SEPARATOR;
124
        // Verify that the upload dir exists and is writable
125
        if(!is_writable($sUploadDir))
126
        {
127
            throw new \Jaxon\Exception\Error($this->trans('errors.upload.access'));
128
        }
129
        $sUploadDir .= $this->sUploadSubdir;
130 View Code Duplication
        if(!file_exists($sUploadDir) && !@mkdir($sUploadDir))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131
        {
132
            throw new \Jaxon\Exception\Error($this->trans('errors.upload.access'));
133
        }
134
        return $sUploadDir;
135
    }
136
137
    /**
138
     * Get the path to the upload temp dir
139
     *
140
     * @return string
141
     */
142
    protected function getUploadTempDir()
143
    {
144
        // Default upload dir
145
        $sUploadDir = $this->getOption('upload.default.dir');
146
        $sUploadDir = rtrim(trim($sUploadDir), '/\\') . DIRECTORY_SEPARATOR;
147
        // Verify that the upload dir exists and is writable
148
        if(!is_writable($sUploadDir))
149
        {
150
            throw new \Jaxon\Exception\Error($this->trans('errors.upload.access'));
151
        }
152
        $sUploadDir .= 'tmp' . DIRECTORY_SEPARATOR;
153 View Code Duplication
        if(!file_exists($sUploadDir) && !@mkdir($sUploadDir))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154
        {
155
            throw new \Jaxon\Exception\Error($this->trans('errors.upload.access'));
156
        }
157
        return $sUploadDir;
158
    }
159
160
    /**
161
     * Get the path to the upload temp file
162
     *
163
     * @return string
164
     */
165
    protected function getUploadTempFile()
166
    {
167
        $sUploadDir = $this->getOption('upload.default.dir');
168
        $sUploadDir = rtrim(trim($sUploadDir), '/\\') . DIRECTORY_SEPARATOR;
169
        $sUploadDir .= 'tmp' . DIRECTORY_SEPARATOR;
170
        $sUploadTempFile = $sUploadDir . $this->sTempFile . '.json';
171
        if(!is_readable($sUploadTempFile))
172
        {
173
            throw new \Jaxon\Exception\Error($this->trans('errors.upload.access'));
174
        }
175
        return $sUploadTempFile;
176
    }
177
178
    /**
179
     * Read uploaded files info from HTTP request data
180
     *
181
     * @return void
182
     */
183
    protected function readFromHttpData()
184
    {
185
        // Check validity of the uploaded files
186
        $aTempFiles = [];
187
        foreach($_FILES as $sVarName => $aFile)
188
        {
189
            if(is_array($aFile['name']))
190
            {
191
                $nFileCount = count($aFile['name']);
192
                for($i = 0; $i < $nFileCount; $i++)
193
                {
194
                    if(!$aFile['name'][$i])
195
                    {
196
                        continue;
197
                    }
198
                    if(!array_key_exists($sVarName, $aTempFiles))
199
                    {
200
                        $aTempFiles[$sVarName] = [];
201
                    }
202
                    // Filename without the extension
203
                    $sFilename = $this->filterFilename(pathinfo($aFile['name'][$i], PATHINFO_FILENAME), $sVarName);
204
                    // Copy the file data into the local array
205
                    $aTempFiles[$sVarName][] = [
206
                        'name' => $aFile['name'][$i],
207
                        'type' => $aFile['type'][$i],
208
                        'tmp_name' => $aFile['tmp_name'][$i],
209
                        'error' => $aFile['error'][$i],
210
                        'size' => $aFile['size'][$i],
211
                        'filename' => $sFilename,
212
                        'extension' => pathinfo($aFile['name'][$i], PATHINFO_EXTENSION),
213
                    ];
214
                }
215
            }
216
            else
217
            {
218
                if(!$aFile['name'])
219
                {
220
                    continue;
221
                }
222
                if(!array_key_exists($sVarName, $aTempFiles))
223
                {
224
                    $aTempFiles[$sVarName] = [];
225
                }
226
                // Filename without the extension
227
                $sFilename = $this->filterFilename(pathinfo($aFile['name'], PATHINFO_FILENAME), $sVarName);
228
                // Copy the file data into the local array
229
                $aTempFiles[$sVarName][] = [
230
                    'name' => $aFile['name'],
231
                    'type' => $aFile['type'],
232
                    'tmp_name' => $aFile['tmp_name'],
233
                    'error' => $aFile['error'],
234
                    'size' => $aFile['size'],
235
                    'filename' => $sFilename,
236
                    'extension' => pathinfo($aFile['name'], PATHINFO_EXTENSION),
237
                ];
238
            }
239
        }
240
241
        // Check uploaded files validity
242
        foreach($aTempFiles as $sVarName => $aFiles)
243
        {
244
            foreach($aFiles as $aFile)
245
            {
246
                // Verify upload result
247
                if($aFile['error'] != 0)
248
                {
249
                    throw new \Jaxon\Exception\Error($this->trans('errors.upload.failed', $aFile));
250
                }
251
                // Verify file validity (format, size)
252
                if(!$this->validateUploadedFile($sVarName, $aFile))
253
                {
254
                    throw new \Jaxon\Exception\Error($this->getValidatorMessage());
255
                }
256
                // Get the path to the upload dir
257
                $this->getUploadDir($sVarName);
258
            }
259
        }
260
261
        // Copy the uploaded files from the temp dir to the user dir
262
        foreach($aTempFiles as $sVarName => $_aTempFiles)
263
        {
264
            $this->aUserFiles[$sVarName] = [];
265
            foreach($_aTempFiles as $aFile)
266
            {
267
                // Get the path to the upload dir
268
                $sUploadDir = $this->getUploadDir($sVarName);
269
                // Set the user file data
270
                $xUploadedFile = UploadedFile::fromHttpData($sUploadDir, $aFile);
271
                // All's right, move the file to the user dir.
272
                move_uploaded_file($aFile["tmp_name"], $xUploadedFile->path());
273
                $this->aUserFiles[$sVarName][] = $xUploadedFile;
274
            }
275
        }
276
    }
277
278
    /**
279
     * Save uploaded files info to a temp file
280
     *
281
     * @return void
282
     */
283
    protected function saveToTempFile()
284
    {
285
        // Convert uploaded file to an array
286
        $aFiles = [];
287
        foreach($this->aUserFiles as $sVarName => $aUserFiles)
288
        {
289
            $aFiles[$sVarName] = [];
290
            foreach($aUserFiles as $aUserFile)
291
            {
292
                 $aFiles[$sVarName][] = $aUserFile->toTempData();
293
            }
294
        }
295
        // Save upload data in a temp file
296
        $sUploadDir = $this->getUploadTempDir();
297
        $this->sTempFile = uniqid();
298
        file_put_contents($sUploadDir . $this->sTempFile . '.json', json_encode($aFiles));
299
    }
300
301
    /**
302
     * Read uploaded files info from a temp file
303
     *
304
     * @return void
305
     */
306
    protected function readFromTempFile()
307
    {
308
        // Upload temp file
309
        $sUploadTempFile = $this->getUploadTempFile();
310
        $aFiles = json_decode(file_get_contents($sUploadTempFile), true);
311
        foreach($aFiles as $sVarName => $aUserFiles)
312
        {
313
            $this->aUserFiles[$sVarName] = [];
314
            foreach($aUserFiles as $aUserFile)
315
            {
316
                $this->aUserFiles[$sVarName][] = UploadedFile::fromTempData($aUserFile);
317
            }
318
        }
319
        // unlink($sUploadTempFile);
320
    }
321
322
    /**
323
     * Return the name of this plugin
324
     *
325
     * @return string
326
     */
327
    public function getName()
328
    {
329
        return Jaxon::FILE_UPLOAD;
330
    }
331
332
    /**
333
     * Filter uploaded file name
334
     *
335
     * @param Closure       $cFileFilter            The closure which filters filenames
336
     *
337
     * @return void
338
     */
339
    public function filter(Closure $cFileFilter)
340
    {
341
        $this->setFileFilter($cFileFilter);
342
    }
343
344
    /**
345
     * Get the uploaded files
346
     *
347
     * @return array
348
     */
349
    public function files()
350
    {
351
        return $this->aUserFiles;
352
    }
353
354
    /**
355
     * Generate a hash for the registered browser events
356
     *
357
     * @return string
358
     */
359
    public function generateHash()
360
    {
361
        return '';
362
    }
363
364
    /**
365
     * Generate client side javascript code for the registered browser events
366
     *
367
     * @return string
368
     */
369
    public function getScript()
370
    {
371
        return '';
372
    }
373
374
    /**
375
     * Inform this plugin that other plugin can process the current request
376
     *
377
     * @return void
378
     */
379
    public function noRequestPluginFound()
380
    {
381
        if(count($_FILES) > 0)
382
        {
383
            $this->bRequestIsHttpUpload = true;
384
        }
385
    }
386
387
    /**
388
     * Check if this plugin can process the incoming Jaxon request
389
     *
390
     * @return boolean
391
     */
392
    public function canProcessRequest()
393
    {
394
        return (count($_FILES) > 0 || ($this->sTempFile));
395
    }
396
397
    /**
398
     * Process the uploaded files into the HTTP request
399
     *
400
     * @return boolean
401
     */
402
    public function processRequest()
403
    {
404
        if(!$this->canProcessRequest())
405
        {
406
            return false;
407
        }
408
409
        if(count($_FILES) > 0)
410
        {
411
            // Ajax request with upload
412
            $this->readFromHttpData();
413
414
            if($this->bRequestIsHttpUpload)
415
            {
416
                // Process an HTTP upload request
417
                // This requires to set the response to be returned.
418
                $xResponse = new UploadResponse();
419
                try
420
                {
421
                    $this->saveToTempFile();
422
                    $xResponse->setUploadedFile($this->sTempFile);
423
                }
424
                catch(Exception $e)
425
                {
426
                    $xResponse->setErrorMessage($e->getMessage());
427
                }
428
                jaxon()->di()->getResponseManager()->append($xResponse);
429
            }
430
        }
431
        elseif(($this->sTempFile))
432
        {
433
            // Ajax request following and HTTP upload
434
            $this->readFromTempFile();
435
        }
436
437
        return true;
438
    }
439
}
440