Completed
Push — master ( 113c9d...6bb3ba )
by Lorenzo
02:38
created

Uploadable::checkIfAllUploadFieldsAreEmpty()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 0
1
<?php
2
3
namespace Padosoft\Uploadable;
4
5
use DB;
6
use Illuminate\Http\UploadedFile;
7
use Illuminate\Database\Eloquent\Model;
8
use Padosoft\Uploadable\Helpers\FileHelper;
9
use Padosoft\Uploadable\Helpers\RequestHelper;
10
use Padosoft\Uploadable\Helpers\UploadedFileHelper;
11
12
/**
13
 * Class Uploadable
14
 * Auto upload and save files on save/create/delete model.
15
 * @package Padosoft\Uploadable
16
 */
17
trait Uploadable
18
{
19
    /** @var UploadOptions */
20
    protected $uploadOptions;
21
22
    /**
23
     * Boot the trait.
24
     */
25
    public static function bootUploadable()
26
    {
27
        static::creating(function ($model) {
28
            $model->uploadOptions = $model->getUploadOptionsOrDefault();
29
            $model->guardAgainstInvalidUploadOptions();
30
        });
31
32
        static::saving(function (Model $model) {
33
            $model->generateAllNewUploadFileNameAndSetAttribute();
34
        });
35
        static::saved(function (Model $model) {
36
            $model->uploadFiles();
37
        });
38
39
        static::updating(function (Model $model) {
40
            $model->generateAllNewUploadFileNameAndSetAttribute();
41
        });
42
        static::updated(function (Model $model) {
43
            $model->uploadFiles();
44
        });
45
46
        static::deleting(function (Model $model) {
47
            $model->uploadOptions = $model->getUploadOptionsOrDefault();
48
            $model->guardAgainstInvalidUploadOptions();
49
        });
50
51
        static::deleting(function (Model $model) {
52
            $model->uploadOptions = $model->getUploadOptionsOrDefault();
53
            $model->guardAgainstInvalidUploadOptions();
54
        });
55
56
        static::deleted(function (Model $model) {
57
            $model->deleteUploadedFiles();
58
        });
59
    }
60
61
    /**
62
     * Retrive a specifice UploadOptions for this model, or return default UploadOptions
63
     * @return UploadOptions
64
     */
65
    public function getUploadOptionsOrDefault() : UploadOptions
66
    {
67
        if (method_exists($this, 'getUploadOptions')) {
68
            $method = 'getUploadOptions';
69
            return $this->{$method}();
70
        } else {
71
            return UploadOptions::create()->getUploadOptionsDefault()
72
                ->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...
73
        }
74
    }
75
76
    /**
77
     * This function will throw an exception when any of the options is missing or invalid.
78
     * @throws InvalidOption
79
     */
80
    public function guardAgainstInvalidUploadOptions()
81
    {
82
        if (!count($this->uploadOptions->uploads)) {
83
            throw InvalidOption::missingUploadFields();
84
        }
85
        if (!strlen($this->uploadOptions->uploadBasePath)) {
86
            throw InvalidOption::missingUploadBasePath();
87
        }
88
    }
89
90
    /**
91
     * Handle file upload.
92
     */
93
    public function uploadFiles()
94
    {
95
        //invalid model
96
        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...
97
            return;
98
        }
99
100
        //current request has not uploaded files
101
        if (!RequestHelper::currentRequestHasFiles()) {
102
            return;
103
        }
104
105
        //ensure that all upload path are ok or create it.
106
        if (!$this->checkOrCreateAllUploadBasePaths()) {
107
            return;
108
        }
109
110
        //loop for every upload model attributes and do upload if has a file in request
111
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
112
            $this->uploadFile($uploadField);
113
        }
114
    }
115
116
    /**
117
     * Upload a file releted to a passed attribute name
118
     * @param string $uploadField
119
     */
120
    public function uploadFile(string $uploadField)
121
    {
122
        //check if there is a file in request for current attribute
123
        $uploadedFile = RequestHelper::getCurrentRequestFileSafe($uploadField, $this->getUploadOptionsOrDefault()->uploadsMimeType);
124
        if (!$uploadedFile) {
125
            return;
126
        }
127
128
        //all ok => do upload
129
        $this->doUpload($uploadedFile, $uploadField);
130
    }
131
132
    /**
133
     * Get an UploadedFile, generate new name, and save it in destination path.
134
     * Return empty string if it fails, otherwise return the saved file name.
135
     * @param UploadedFile $uploadedFile
136
     * @param string $uploadAttribute
137
     * @return string
138
     */
139
    public function doUpload(UploadedFile $uploadedFile, $uploadAttribute) : string
140
    {
141
        if ($this->id < 1) {
142
            return '';
143
        }
144
145
        if (!$uploadedFile || !$uploadAttribute) {
146
            return '';
147
        }
148
149
        //get file name by attribute
150
        $newName = $this->{$uploadAttribute};
151
152
        //get upload path to store
153
        $pathToStore = $this->getUploadFilePath($uploadAttribute);
154
155
        //delete if file already exists
156
        FileHelper::unlinkSafe($pathToStore . '/' . $newName);
157
158
        //move uploaded file to destination folder
159
        try {
160
            $targetFile = $uploadedFile->move($pathToStore, $newName);
161
        } catch (\Symfony\Component\HttpFoundation\File\Exception\FileException $e) {
162
            Log::warning('Error in doUpload() when try to move '.$newName.' to folder: '.$pathToStore.PHP_EOL.$e->getMessage().PHP_EOL.$e->getTraceAsString());
163
            return '';
164
        }
165
166
        return $targetFile ? $newName : '';
167
    }
168
169
    /**
170
     * Generate a new file name for uploaded file.
171
     * Return empty string if uploadedFile is null, otherwise return the new file name..
172
     * @param UploadedFile $uploadedFile
173
     * @param string $uploadField
174
     * @return string
175
     */
176
    public function generateNewUploadFileName(UploadedFile $uploadedFile, string $uploadField) : string
177
    {
178
        if (!$uploadField) {
179
            return '';
180
        }
181
        if (!$uploadedFile) {
182
            return '';
183
        }
184
185
        //check if file need a new name
186
        $newName = $this->calcolateNewUploadFileName($uploadedFile);
187
        if($newName!=''){
188
            return $newName;
189
        }
190
191
        //no new file name, return original file name
192
        return $uploadedFile->getFilename();
193
    }
194
195
    /**
196
     * Check if file need a new name and return it, otherwise return empty string.
197
     * @param UploadedFile $uploadedFile
198
     * @return string
199
     */
200
    protected function calcolateNewUploadFileName(UploadedFile $uploadedFile) : string
201
    {
202
        if (!$this->getUploadOptionsOrDefault()->appendModelIdSuffixInUploadedFileName) {
203
            return '';
204
        }
205
206
        //retrive original file name and extension
207
        $filenameWithoutExtension = UploadedFileHelper::getFilenameWithoutExtension($uploadedFile);
208
        $ext = $uploadedFile->getClientOriginalExtension();
209
210
        $newName = $filenameWithoutExtension . $this->getUploadOptionsOrDefault()->uploadFileNameSuffixSeparator . $this->id . '.' . $ext;
211
        return $newName;
212
    }
213
214
    /**
215
     * delete all Uploaded Files
216
     */
217
    public function deleteUploadedFiles()
218
    {
219
        //loop for every upload model attributes
220
        foreach ($this->getUploadOptionsOrDefault()->uploads as $uploadField) {
221
            $this->deleteUploadedFile($uploadField);
222
        }
223
    }
224
225
    /**
226
     * Delete upload file related to passed attribute name
227
     * @param string $uploadField
228
     */
229
    public function deleteUploadedFile(string $uploadField)
230
    {
231
        //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...
232
        if (!$uploadField) {
233
            return;
234
        }
235
236
        //if a blank attribute value skip it
237
        if (!$this->{$uploadField}) {
238
            return;
239
        }
240
241
        //retrive correct upload storage path for current attribute
242
        $uploadFieldPath = $this->getUploadFilePath($uploadField);
243
244
        //unlink file
245
        $path = sprintf("%s/%s", $uploadFieldPath, $this->{$uploadField});
246
        FileHelper::unlinkSafe($path);
247
248
        //reset model attribute and update db field
249
        $this->setBlanckAttributeAndDB($uploadField);
250
    }
251
252
    /**
253
     * Reset model attribute and update db field
254
     * @param string $uploadField
255
     */
256
    protected function setBlanckAttributeAndDB(string $uploadField)
257
    {
258
        //set to black attribute
259
        $this->{$uploadField} = '';
260
261
        //save on db (not call model save because invoke event and entering in loop)
262
        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...
263
            ->where('id', $this->id)
264
            ->update([$uploadField => '']);
265
    }
266
267
    /**
268
     * Return true If All Upload atrributes Are Empty or
269
     * if the uploads array is not set.
270
     * @return bool
271
     */
272
    public function checkIfAllUploadFieldsAreEmpty() : bool
273
    {
274
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
275
            //for performance if one attribute has value exit false
276
            if ($this-> {$uploadField}) {
277
                return false;
278
            }
279
        }
280
281
        return true;
282
    }
283
284
    /**
285
     * Check all attributes upload path, and try to create dir if not already exists.
286
     * Return false if it fails to create all founded dirs.
287
     * @return bool
288
     */
289
    public function checkOrCreateAllUploadBasePaths() : bool
290
    {
291
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
292
            if (!$this->checkOrCreateUploadBasePath($uploadField)) {
293
                return false;
294
            }
295
        }
296
297
        return true;
298
    }
299
300
    /**
301
     * Check uploads property and return a uploads class field
302
     * or empty array if somethings wrong.
303
     * @return array
304
     */
305
    public function getUploadsAttributesSafe() : array
306
    {
307
        if (!is_array($this->getUploadOptionsOrDefault()->uploads)) {
308
            return [];
309
        }
310
311
        return $this->getUploadOptionsOrDefault()->uploads;
312
    }
313
314
    /**
315
     * Check attribute upload path, and try to create dir if not already exists.
316
     * Return false if it fails to create the dir.
317
     * @param string $uploadField
318
     * @return bool
319
     */
320
    public function checkOrCreateUploadBasePath(string $uploadField) : bool
321
    {
322
        $uploadFieldPath = $this->getUploadFilePath($uploadField);
323
324
        return FileHelper::checkDirExistOrCreate($uploadFieldPath, $this->getUploadOptionsOrDefault()->uploadCreateDirModeMask);
325
    }
326
327
    /**
328
     * Return the upload path for the passed attribute and try to create it if not exists.
329
     * Returns empty string if dir if not exists and fails to create it.
330
     * @param string $uploadField
331
     * @return string
332
     */
333
    public function getUploadFilePath(string $uploadField) : string
334
    {
335
        //default model upload path
336
        $uploadFieldPath = $this->getUploadOptionsOrDefault()->uploadBasePath;
337
338
        //overwrite if there is specific path for the field
339
        $specificPath = $this->getUploadFilePathSpecific($uploadField);
340
        if($specificPath!=''){
341
            $uploadFieldPath = $specificPath;
342
        }
343
344
        //check if exists or try to create dir
345
        if(!FileHelper::checkDirExistOrCreate($uploadFieldPath,$this->getUploadOptionsOrDefault()->uploadCreateDirModeMask)){
346
            return '';
347
        }
348
349
        return $uploadFieldPath;
350
    }
351
352
    /**
353
     * Return the specific upload path (by uploadPaths prop) for the passed attribute if exists.
354
     * @param string $uploadField
355
     * @return string
356
     */
357
    public function getUploadFilePathSpecific(string $uploadField) : string
358
    {
359
        //check if there is a specified upload path
360
        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...
361
        $this->getUploadOptionsOrDefault()->uploadPaths)
362
        ) {
363
            return public_path($this->getUploadOptionsOrDefault()->uploadPaths[$uploadField]);
364
        }
365
366
        return '';
367
    }
368
369
    /**
370
     * Calcolate the new name for ALL uploaded files and set relative upload attributes
371
     */
372
    public function generateAllNewUploadFileNameAndSetAttribute()
373
    {
374
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
375
            $this->generateNewUploadFileNameAndSetAttribute($uploadField);
376
        }
377
    }
378
379
    /**
380
     * Calcolate the new name for uploaded file relative to passed attribute name and set the upload attribute
381
     * @param string $uploadField
382
     */
383
    public function generateNewUploadFileNameAndSetAttribute(string $uploadField)
384
    {
385
        if($uploadField==''){
386
            return;
387
        }
388
389
        //generate new file name
390
        $uploadedFile = $this->getCurrentRequestFileSafe($uploadField);
0 ignored issues
show
Bug introduced by
It seems like getCurrentRequestFileSafe() 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...
391
        $newName = $this->generateNewUploadFileName($uploadedFile, $uploadField);
392
        if($newName==''){
393
            return;
394
        }
395
396
        //set attribute
397
        $this->{$uploadField} = $newName;
398
    }
399
}
400