Completed
Push — master ( 3db3b2...887921 )
by Thierry
01:39
created

FileUpload   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 421
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
dl 0
loc 421
rs 8.48
c 0
b 0
f 0
wmc 49
lcom 1
cbo 10

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 3
A setFileFilter() 0 4 1
A filterFilename() 0 9 2
A _makeUploadDir() 0 15 4
A getUploadDir() 0 8 1
A getUploadTempDir() 0 7 1
A getUploadTempFile() 0 12 2
C readFromHttpData() 0 94 14
A saveToTempFile() 0 17 3
A readFromTempFile() 0 15 3
A getName() 0 4 1
A filter() 0 4 1
A files() 0 4 1
A generateHash() 0 4 1
A getScript() 0 4 1
A noRequestPluginFound() 0 7 2
A canProcessRequest() 0 4 2
B processRequest() 0 37 6

How to fix   Complexity   

Complex Class

Complex classes like FileUpload often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileUpload, and based on these observations, apply Extract Interface, too.

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