Completed
Push — master ( 571b23...1a8b44 )
by Lorenzo
02:04
created

Uploadable::uploadFiles()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 4
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
        $this->doUpload($uploadedFile, $uploadField);
125
    }
126
127
    /**
128
     * Get an UploadedFile, generate new name, and save it in destination path.
129
     * Return empty string if it fails, otherwise return the saved file name.
130
     * @param UploadedFile $uploadedFile
131
     * @param string $uploadAttribute
132
     * @return string
133
     */
134
    public function doUpload(UploadedFile $uploadedFile, $uploadAttribute) : string
135
    {
136
        if (($this->id < 1) || !$uploadedFile || !$uploadAttribute) {
137
            return '';
138
        }
139
140
        //get file name by attribute
141
        $newName = $this->{$uploadAttribute};
142
143
        //get upload path to store
144
        $pathToStore = $this->getUploadFilePath($uploadAttribute);
145
146
        //delete if file already exists
147
        FileHelper::unlinkSafe($pathToStore . '/' . $newName);
148
149
        //move file to destination folder
150
        try {
151
            $targetFile = $uploadedFile->move($pathToStore, $newName);
152
        } catch (\Symfony\Component\HttpFoundation\File\Exception\FileException $e) {
153
            Log::warning('Error in doUpload() when try to move '.$newName.' to folder: '.$pathToStore.PHP_EOL.$e->getMessage().PHP_EOL.$e->getTraceAsString());
154
            return '';
155
        }
156
157
        return $targetFile ? $newName : '';
158
    }
159
160
    /**
161
     * Check request for valid files and server for correct paths defined in model
162
     * @return bool
163
     */
164
    protected function requestHasValidFilesAndCorrectPaths() : bool
165
    {
166
        //current request has not uploaded files
167
        if (!RequestHelper::currentRequestHasFiles()) {
168
            return false;
169
        }
170
171
        //ensure that all upload path are ok or create it.
172
        if (!$this->checkOrCreateAllUploadBasePaths()) {
173
            return false;
174
        }
175
176
        return true;
177
    }
178
179
    /**
180
     * Generate a new file name for uploaded file.
181
     * Return empty string if uploadedFile is null, otherwise return the new file name..
182
     * @param UploadedFile $uploadedFile
183
     * @param string $uploadField
184
     * @return string
185
     */
186
    public function generateNewUploadFileName(UploadedFile $uploadedFile, string $uploadField) : string
187
    {
188
        if (!$uploadField) {
189
            return '';
190
        }
191
        if (!$uploadedFile) {
192
            return '';
193
        }
194
195
        //check if file need a new name
196
        $newName = $this->calcolateNewUploadFileName($uploadedFile);
197
        if($newName!=''){
198
            return $newName;
199
        }
200
201
        //no new file name, return original file name
202
        return $uploadedFile->getFilename();
203
    }
204
205
    /**
206
     * Check if file need a new name and return it, otherwise return empty string.
207
     * @param UploadedFile $uploadedFile
208
     * @return string
209
     */
210
    protected function calcolateNewUploadFileName(UploadedFile $uploadedFile) : string
211
    {
212
        if (!$this->getUploadOptionsOrDefault()->appendModelIdSuffixInUploadedFileName) {
213
            return '';
214
        }
215
216
        //retrive original file name and extension
217
        $filenameWithoutExtension = UploadedFileHelper::getFilenameWithoutExtension($uploadedFile);
218
        $ext = $uploadedFile->getClientOriginalExtension();
219
220
        $newName = $filenameWithoutExtension . $this->getUploadOptionsOrDefault()->uploadFileNameSuffixSeparator . $this->id . '.' . $ext;
221
        return $newName;
222
    }
223
224
    /**
225
     * delete all Uploaded Files
226
     */
227
    public function deleteUploadedFiles()
228
    {
229
        //loop for every upload model attributes
230
        foreach ($this->getUploadOptionsOrDefault()->uploads as $uploadField) {
231
            $this->deleteUploadedFile($uploadField);
232
        }
233
    }
234
235
    /**
236
     * Delete upload file related to passed attribute name
237
     * @param string $uploadField
238
     */
239
    public function deleteUploadedFile(string $uploadField)
240
    {
241
        if (!$uploadField) {
242
            return;
243
        }
244
245
        if (!$this->{$uploadField}) {
246
            return;
247
        }
248
249
        //retrive correct upload storage path for current attribute
250
        $uploadFieldPath = $this->getUploadFilePath($uploadField);
251
252
        //unlink file
253
        $path = sprintf("%s/%s", $uploadFieldPath, $this->{$uploadField});
254
        FileHelper::unlinkSafe($path);
255
256
        //reset model attribute and update db field
257
        $this->setBlanckAttributeAndDB($uploadField);
258
    }
259
260
    /**
261
     * Reset model attribute and update db field
262
     * @param string $uploadField
263
     */
264
    protected function setBlanckAttributeAndDB(string $uploadField)
265
    {
266
        //set to black attribute
267
        $this->{$uploadField} = '';
268
269
        //save on db (not call model save because invoke event and entering in loop)
270
        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...
271
            ->where('id', $this->id)
272
            ->update([$uploadField => '']);
273
    }
274
275
    /**
276
     * Return true If All Upload atrributes Are Empty or
277
     * if the uploads array is not set.
278
     * @return bool
279
     */
280
    public function checkIfAllUploadFieldsAreEmpty() : bool
281
    {
282
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
283
            //for performance if one attribute has value exit false
284
            if ($this-> {$uploadField}) {
285
                return false;
286
            }
287
        }
288
289
        return true;
290
    }
291
292
    /**
293
     * Check all attributes upload path, and try to create dir if not already exists.
294
     * Return false if it fails to create all founded dirs.
295
     * @return bool
296
     */
297
    public function checkOrCreateAllUploadBasePaths() : bool
298
    {
299
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
300
            if (!$this->checkOrCreateUploadBasePath($uploadField)) {
301
                return false;
302
            }
303
        }
304
305
        return true;
306
    }
307
308
    /**
309
     * Check uploads property and return a uploads class field
310
     * or empty array if somethings wrong.
311
     * @return array
312
     */
313
    public function getUploadsAttributesSafe() : array
314
    {
315
        if (!is_array($this->getUploadOptionsOrDefault()->uploads)) {
316
            return [];
317
        }
318
319
        return $this->getUploadOptionsOrDefault()->uploads;
320
    }
321
322
    /**
323
     * Check attribute upload path, and try to create dir if not already exists.
324
     * Return false if it fails to create the dir.
325
     * @param string $uploadField
326
     * @return bool
327
     */
328
    public function checkOrCreateUploadBasePath(string $uploadField) : bool
329
    {
330
        $uploadFieldPath = $this->getUploadFilePath($uploadField);
331
332
        return FileHelper::checkDirExistOrCreate($uploadFieldPath, $this->getUploadOptionsOrDefault()->uploadCreateDirModeMask);
333
    }
334
335
    /**
336
     * Return the upload path for the passed attribute and try to create it if not exists.
337
     * Returns empty string if dir if not exists and fails to create it.
338
     * @param string $uploadField
339
     * @return string
340
     */
341
    public function getUploadFilePath(string $uploadField) : string
342
    {
343
        //default model upload path
344
        $uploadFieldPath = $this->getUploadOptionsOrDefault()->uploadBasePath;
345
346
        //overwrite if there is specific path for the field
347
        $specificPath = $this->getUploadFilePathSpecific($uploadField);
348
        if($specificPath!=''){
349
            $uploadFieldPath = $specificPath;
350
        }
351
352
        //check if exists or try to create dir
353
        if(!FileHelper::checkDirExistOrCreate($uploadFieldPath,$this->getUploadOptionsOrDefault()->uploadCreateDirModeMask)){
354
            return '';
355
        }
356
357
        return $uploadFieldPath;
358
    }
359
360
    /**
361
     * Return the specific upload path (by uploadPaths prop) for the passed attribute if exists.
362
     * @param string $uploadField
363
     * @return string
364
     */
365
    public function getUploadFilePathSpecific(string $uploadField) : string
366
    {
367
        //check if there is a specified upload path
368
        if (!empty($this->getUploadOptionsOrDefault()->uploadPaths) && count($this->getUploadOptionsOrDefault()->uploadPaths) > 0 && array_key_exists($uploadField,
369
        $this->getUploadOptionsOrDefault()->uploadPaths)
370
        ) {
371
            return public_path($this->getUploadOptionsOrDefault()->uploadPaths[$uploadField]);
372
        }
373
374
        return '';
375
    }
376
377
    /**
378
     * Calcolate the new name for ALL uploaded files and set relative upload attributes
379
     */
380
    public function generateAllNewUploadFileNameAndSetAttribute()
381
    {
382
        foreach ($this->getUploadsAttributesSafe() as $uploadField) {
383
            $this->generateNewUploadFileNameAndSetAttribute($uploadField);
384
        }
385
    }
386
387
    /**
388
     * Calcolate the new name for uploaded file relative to passed attribute name and set the upload attribute
389
     * @param string $uploadField
390
     */
391
    public function generateNewUploadFileNameAndSetAttribute(string $uploadField)
392
    {
393
        if($uploadField===null || trim($uploadField)==''){
394
            return;
395
        }
396
397
        //generate new file name
398
        $uploadedFile = RequestHelper::getCurrentRequestFileSafe($uploadField);
399
        if($uploadedFile===null){
400
            return;
401
        }
402
        $newName = $this->generateNewUploadFileName($uploadedFile, $uploadField);
403
        if($newName==''){
404
            return;
405
        }
406
407
        //set attribute
408
        $this->{$uploadField} = $newName;
409
    }
410
}
411