Completed
Push — master ( aafc3c...571b23 )
by Lorenzo
02:05
created

Uploadable::requestHasValidFilesAndCorrectPaths()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 6
nc 3
nop 0
1
<?php
2
3
namespace Padosoft\Uploadable;
4
5
use DB;
6
use Illuminate\Support\Facades\Log;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Http\UploadedFile;
9
use Padosoft\Uploadable\Helpers\FileHelper;
10
use Padosoft\Uploadable\Helpers\RequestHelper;
11
use Padosoft\Uploadable\Helpers\UploadedFileHelper;
12
13
/**
14
 * Class Uploadable
15
 * Auto upload and save files on save/create/delete model.
16
 * @package Padosoft\Uploadable
17
 */
18
trait Uploadable
19
{
20
    /** @var UploadOptions */
21
    protected $uploadOptions;
22
23
    /**
24
     * Boot the trait.
25
     */
26
    public static function bootUploadable()
27
    {
28
        static::creating(function ($model) {
29
            $model->uploadOptions = $model->getUploadOptionsOrDefault();
30
            $model->guardAgainstInvalidUploadOptions();
31
        });
32
33
        static::saving(function (Model $model) {
34
            $model->generateAllNewUploadFileNameAndSetAttribute();
35
        });
36
        static::saved(function (Model $model) {
37
            $model->uploadFiles();
38
        });
39
40
        static::updating(function (Model $model) {
41
            $model->generateAllNewUploadFileNameAndSetAttribute();
42
        });
43
        static::updated(function (Model $model) {
44
            $model->uploadFiles();
45
        });
46
47
        static::deleting(function (Model $model) {
48
            $model->uploadOptions = $model->getUploadOptionsOrDefault();
49
            $model->guardAgainstInvalidUploadOptions();
50
        });
51
52
        static::deleting(function (Model $model) {
53
            $model->uploadOptions = $model->getUploadOptionsOrDefault();
54
            $model->guardAgainstInvalidUploadOptions();
55
        });
56
57
        static::deleted(function (Model $model) {
58
            $model->deleteUploadedFiles();
59
        });
60
    }
61
62
    /**
63
     * Retrive a specifice UploadOptions for this model, or return default UploadOptions
64
     * @return UploadOptions
65
     */
66
    public function getUploadOptionsOrDefault() : UploadOptions
67
    {
68
        if (method_exists($this, 'getUploadOptions')) {
69
            $method = 'getUploadOptions';
70
            return $this->{$method}();
71
        } else {
72
            return UploadOptions::create()->getUploadOptionsDefault()
73
                ->setUploadBasePath(public_path('upload/' . $this->getTable()));
0 ignored issues
show
Bug introduced by
It seems like getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
74
        }
75
    }
76
77
    /**
78
     * This function will throw an exception when any of the options is missing or invalid.
79
     * @throws InvalidOption
80
     */
81
    public function guardAgainstInvalidUploadOptions()
82
    {
83
        if (!count($this->uploadOptions->uploads)) {
84
            throw InvalidOption::missingUploadFields();
85
        }
86
        if (!strlen($this->uploadOptions->uploadBasePath)) {
87
            throw InvalidOption::missingUploadBasePath();
88
        }
89
    }
90
91
    /**
92
     * Handle file upload.
93
     */
94
    public function uploadFiles()
95
    {
96
        //invalid model
97
        if ($this->id < 1) {
0 ignored issues
show
Bug introduced by
The property id does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
98
            return;
99
        }
100
101
        //check request for valid files and server for correct paths defined in model
102
        if(!$this->requestHasValidFilesAndCorrectPaths()){
103
            return;
104
        }
105
106
        //loop for every upload model attributes and do upload if has a file in request
107
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
108
            $this->uploadFile($uploadField);
109
        }
110
    }
111
112
    /**
113
     * Upload a file releted to a passed attribute name
114
     * @param string $uploadField
115
     */
116
    public function uploadFile(string $uploadField)
117
    {
118
        //check if there is a file in request for current attribute
119
        $uploadedFile = RequestHelper::getCurrentRequestFileSafe($uploadField, $this->getUploadOptionsOrDefault()->uploadsMimeType);
120
        if (!$uploadedFile) {
121
            return;
122
        }
123
124
        //all ok => do upload
125
        $this->doUpload($uploadedFile, $uploadField);
126
    }
127
128
    /**
129
     * Get an UploadedFile, generate new name, and save it in destination path.
130
     * Return empty string if it fails, otherwise return the saved file name.
131
     * @param UploadedFile $uploadedFile
132
     * @param string $uploadAttribute
133
     * @return string
134
     */
135
    public function doUpload(UploadedFile $uploadedFile, $uploadAttribute) : string
136
    {
137
        if (($this->id < 1) || !$uploadedFile || !$uploadAttribute) {
138
            return '';
139
        }
140
141
        //get file name by attribute
142
        $newName = $this->{$uploadAttribute};
143
144
        //get upload path to store
145
        $pathToStore = $this->getUploadFilePath($uploadAttribute);
146
147
        //delete if file already exists
148
        FileHelper::unlinkSafe($pathToStore . '/' . $newName);
149
150
        //move uploaded file to destination folder
151
        try {
152
            $targetFile = $uploadedFile->move($pathToStore, $newName);
153
        } catch (\Symfony\Component\HttpFoundation\File\Exception\FileException $e) {
154
            Log::warning('Error in doUpload() when try to move '.$newName.' to folder: '.$pathToStore.PHP_EOL.$e->getMessage().PHP_EOL.$e->getTraceAsString());
155
            return '';
156
        }
157
158
        return $targetFile ? $newName : '';
159
    }
160
161
    /**
162
     * Check request for valid files and server for correct paths defined in model
163
     * @return bool
164
     */
165
    protected function requestHasValidFilesAndCorrectPaths() : bool
166
    {
167
        //current request has not uploaded files
168
        if (!RequestHelper::currentRequestHasFiles()) {
169
            return false;
170
        }
171
172
        //ensure that all upload path are ok or create it.
173
        if (!$this->checkOrCreateAllUploadBasePaths()) {
174
            return false;
175
        }
176
177
        return true;
178
    }
179
180
    /**
181
     * Generate a new file name for uploaded file.
182
     * Return empty string if uploadedFile is null, otherwise return the new file name..
183
     * @param UploadedFile $uploadedFile
184
     * @param string $uploadField
185
     * @return string
186
     */
187
    public function generateNewUploadFileName(UploadedFile $uploadedFile, string $uploadField) : string
188
    {
189
        if (!$uploadField) {
190
            return '';
191
        }
192
        if (!$uploadedFile) {
193
            return '';
194
        }
195
196
        //check if file need a new name
197
        $newName = $this->calcolateNewUploadFileName($uploadedFile);
198
        if($newName!=''){
199
            return $newName;
200
        }
201
202
        //no new file name, return original file name
203
        return $uploadedFile->getFilename();
204
    }
205
206
    /**
207
     * Check if file need a new name and return it, otherwise return empty string.
208
     * @param UploadedFile $uploadedFile
209
     * @return string
210
     */
211
    protected function calcolateNewUploadFileName(UploadedFile $uploadedFile) : string
212
    {
213
        if (!$this->getUploadOptionsOrDefault()->appendModelIdSuffixInUploadedFileName) {
214
            return '';
215
        }
216
217
        //retrive original file name and extension
218
        $filenameWithoutExtension = UploadedFileHelper::getFilenameWithoutExtension($uploadedFile);
219
        $ext = $uploadedFile->getClientOriginalExtension();
220
221
        $newName = $filenameWithoutExtension . $this->getUploadOptionsOrDefault()->uploadFileNameSuffixSeparator . $this->id . '.' . $ext;
222
        return $newName;
223
    }
224
225
    /**
226
     * delete all Uploaded Files
227
     */
228
    public function deleteUploadedFiles()
229
    {
230
        //loop for every upload model attributes
231
        foreach ($this->getUploadOptionsOrDefault()->uploads as $uploadField) {
232
            $this->deleteUploadedFile($uploadField);
233
        }
234
    }
235
236
    /**
237
     * Delete upload file related to passed attribute name
238
     * @param string $uploadField
239
     */
240
    public function deleteUploadedFile(string $uploadField)
241
    {
242
        //if empty prop exit
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
243
        if (!$uploadField) {
244
            return;
245
        }
246
247
        //if a blank attribute value skip it
248
        if (!$this->{$uploadField}) {
249
            return;
250
        }
251
252
        //retrive correct upload storage path for current attribute
253
        $uploadFieldPath = $this->getUploadFilePath($uploadField);
254
255
        //unlink file
256
        $path = sprintf("%s/%s", $uploadFieldPath, $this->{$uploadField});
257
        FileHelper::unlinkSafe($path);
258
259
        //reset model attribute and update db field
260
        $this->setBlanckAttributeAndDB($uploadField);
261
    }
262
263
    /**
264
     * Reset model attribute and update db field
265
     * @param string $uploadField
266
     */
267
    protected function setBlanckAttributeAndDB(string $uploadField)
268
    {
269
        //set to black attribute
270
        $this->{$uploadField} = '';
271
272
        //save on db (not call model save because invoke event and entering in loop)
273
        DB::table($this->getTable())
0 ignored issues
show
Bug introduced by
It seems like getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
274
            ->where('id', $this->id)
275
            ->update([$uploadField => '']);
276
    }
277
278
    /**
279
     * Return true If All Upload atrributes Are Empty or
280
     * if the uploads array is not set.
281
     * @return bool
282
     */
283
    public function checkIfAllUploadFieldsAreEmpty() : bool
284
    {
285
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
286
            //for performance if one attribute has value exit false
287
            if ($this-> {$uploadField}) {
288
                return false;
289
            }
290
        }
291
292
        return true;
293
    }
294
295
    /**
296
     * Check all attributes upload path, and try to create dir if not already exists.
297
     * Return false if it fails to create all founded dirs.
298
     * @return bool
299
     */
300
    public function checkOrCreateAllUploadBasePaths() : bool
301
    {
302
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
303
            if (!$this->checkOrCreateUploadBasePath($uploadField)) {
304
                return false;
305
            }
306
        }
307
308
        return true;
309
    }
310
311
    /**
312
     * Check uploads property and return a uploads class field
313
     * or empty array if somethings wrong.
314
     * @return array
315
     */
316
    public function getUploadsAttributesSafe() : array
317
    {
318
        if (!is_array($this->getUploadOptionsOrDefault()->uploads)) {
319
            return [];
320
        }
321
322
        return $this->getUploadOptionsOrDefault()->uploads;
323
    }
324
325
    /**
326
     * Check attribute upload path, and try to create dir if not already exists.
327
     * Return false if it fails to create the dir.
328
     * @param string $uploadField
329
     * @return bool
330
     */
331
    public function checkOrCreateUploadBasePath(string $uploadField) : bool
332
    {
333
        $uploadFieldPath = $this->getUploadFilePath($uploadField);
334
335
        return FileHelper::checkDirExistOrCreate($uploadFieldPath, $this->getUploadOptionsOrDefault()->uploadCreateDirModeMask);
336
    }
337
338
    /**
339
     * Return the upload path for the passed attribute and try to create it if not exists.
340
     * Returns empty string if dir if not exists and fails to create it.
341
     * @param string $uploadField
342
     * @return string
343
     */
344
    public function getUploadFilePath(string $uploadField) : string
345
    {
346
        //default model upload path
347
        $uploadFieldPath = $this->getUploadOptionsOrDefault()->uploadBasePath;
348
349
        //overwrite if there is specific path for the field
350
        $specificPath = $this->getUploadFilePathSpecific($uploadField);
351
        if($specificPath!=''){
352
            $uploadFieldPath = $specificPath;
353
        }
354
355
        //check if exists or try to create dir
356
        if(!FileHelper::checkDirExistOrCreate($uploadFieldPath,$this->getUploadOptionsOrDefault()->uploadCreateDirModeMask)){
357
            return '';
358
        }
359
360
        return $uploadFieldPath;
361
    }
362
363
    /**
364
     * Return the specific upload path (by uploadPaths prop) for the passed attribute if exists.
365
     * @param string $uploadField
366
     * @return string
367
     */
368
    public function getUploadFilePathSpecific(string $uploadField) : string
369
    {
370
        //check if there is a specified upload path
371
        if ($this->getUploadOptionsOrDefault()->uploadPaths && count($this->getUploadOptionsOrDefault()->uploadPaths) > 0 && array_key_exists($uploadField,
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getUploadOptionsOrDefault()->uploadPaths of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
372
        $this->getUploadOptionsOrDefault()->uploadPaths)
373
        ) {
374
            return public_path($this->getUploadOptionsOrDefault()->uploadPaths[$uploadField]);
375
        }
376
377
        return '';
378
    }
379
380
    /**
381
     * Calcolate the new name for ALL uploaded files and set relative upload attributes
382
     */
383
    public function generateAllNewUploadFileNameAndSetAttribute()
384
    {
385
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
386
            $this->generateNewUploadFileNameAndSetAttribute($uploadField);
387
        }
388
    }
389
390
    /**
391
     * Calcolate the new name for uploaded file relative to passed attribute name and set the upload attribute
392
     * @param string $uploadField
393
     */
394
    public function generateNewUploadFileNameAndSetAttribute(string $uploadField)
395
    {
396
        if($uploadField===null || trim($uploadField)==''){
397
            return;
398
        }
399
400
        //generate new file name
401
        $uploadedFile = RequestHelper::getCurrentRequestFileSafe($uploadField);
402
        $newName = $this->generateNewUploadFileName($uploadedFile, $uploadField);
0 ignored issues
show
Bug introduced by
It seems like $uploadedFile defined by \Padosoft\Uploadable\Hel...tFileSafe($uploadField) on line 401 can be null; however, Padosoft\Uploadable\Uplo...rateNewUploadFileName() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
403
        if($newName==''){
404
            return;
405
        }
406
407
        //set attribute
408
        $this->{$uploadField} = $newName;
409
    }
410
}
411