UploadBehavior::_getUploadPath()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 17
rs 9.4285
cc 3
eloc 10
nc 2
nop 3
1
<?php
2
namespace Xety\Cake3Upload\Model\Behavior;
3
4
use Cake\Event\Event;
5
use Cake\Filesystem\File;
6
use Cake\Filesystem\Folder;
7
use Cake\ORM\Behavior;
8
use Cake\ORM\Entity;
9
10
class UploadBehavior extends Behavior
11
{
12
13
    /**
14
     * Default config.
15
     *
16
     * @var array
17
     */
18
    protected $_defaultConfig = [
19
        'root' => WWW_ROOT,
20
        'suffix' => '_file',
21
        'fields' => []
22
    ];
23
24
    /**
25
     * Overwrite all file on upload.
26
     *
27
     * @var bool
28
     */
29
    protected $_overwrite = true;
30
31
    /**
32
     * The prefix of the file.
33
     *
34
     * @var bool|string
35
     */
36
    protected $_prefix = false;
37
38
    /**
39
     * The default file of the field.
40
     *
41
     * @var bool|string
42
     */
43
    protected $_defaultFile = false;
44
45
    /**
46
     * Check if there is some files to upload and modify the entity before
47
     * it is saved.
48
     *
49
     * At the end, for each files to upload, unset their "virtual" property.
50
     *
51
     * @param Event  $event  The beforeSave event that was fired.
52
     * @param Entity $entity The entity that is going to be saved.
53
     *
54
     * @throws \LogicException When the path configuration is not set.
55
     * @throws \ErrorException When the function to get the upload path failed.
56
     *
57
     * @return void
58
     */
59
    public function beforeSave(Event $event, Entity $entity)
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
60
    {
61
        $config = $this->_config;
62
63
        foreach ($config['fields'] as $field => $fieldOption) {
64
            $data = $entity->toArray();
65
            $virtualField = $field . $config['suffix'];
66
67
            if (!isset($data[$virtualField]) || !is_array($data[$virtualField])) {
68
                continue;
69
            }
70
71
            $file = $entity->get($virtualField);
72
73
            $error = $this->_triggerErrors($file);
74
75
            if ($error === false) {
76
                continue;
77
            } elseif (is_string($error)) {
78
                throw new \ErrorException($error);
79
            }
80
81
            if (!isset($fieldOption['path'])) {
82
                throw new \LogicException(__('The path for the {0} field is required.', $field));
83
            }
84
85 View Code Duplication
            if (isset($fieldOption['prefix']) && (is_bool($fieldOption['prefix']) || is_string($fieldOption['prefix']))) {
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...
86
                $this->_prefix = $fieldOption['prefix'];
87
            }
88
89
            $extension = (new File($file['name'], false))->ext();
90
            $uploadPath = $this->_getUploadPath($entity, $fieldOption['path'], $extension);
91
            if (!$uploadPath) {
92
                throw new \ErrorException(__('Error to get the uploadPath.'));
93
            }
94
95
            $folder = new Folder($this->_config['root']);
96
            $folder->create($this->_config['root'] . dirname($uploadPath));
97
98
            if ($this->_moveFile($entity, $file['tmp_name'], $uploadPath, $field, $fieldOption)) {
99
                if (!$this->_prefix) {
100
                    $this->_prefix = '';
101
                }
102
103
                $entity->set($field, $this->_prefix . $uploadPath);
104
            }
105
106
            $entity->unsetProperty($virtualField);
107
        }
108
    }
109
110
    /**
111
     * Trigger upload errors.
112
     *
113
     * @param  array $file The file to check.
114
     *
115
     * @return string|int|void
116
     */
117
    protected function _triggerErrors($file)
118
    {
119
        if (!empty($file['error'])) {
120
            switch ((int)$file['error']) {
121
                case UPLOAD_ERR_INI_SIZE:
122
                    $message = __('The uploaded file exceeds the upload_max_filesize directive in php.ini : {0}', ini_get('upload_max_filesize'));
123
                    break;
124
125
                case UPLOAD_ERR_FORM_SIZE:
126
                    $message = __('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
127
                    break;
128
129
                case UPLOAD_ERR_NO_FILE:
130
                    $message = false;
131
                    break;
132
133
                case UPLOAD_ERR_PARTIAL:
134
                    $message = __('The uploaded file was only partially uploaded.');
135
                    break;
136
137
                case UPLOAD_ERR_NO_TMP_DIR:
138
                    $message = __('Missing a temporary folder.');
139
                    break;
140
141
                case UPLOAD_ERR_CANT_WRITE:
142
                    $message = __('Failed to write file to disk.');
143
                    break;
144
145
                case UPLOAD_ERR_EXTENSION:
146
                    $message = __('A PHP extension stopped the file upload.');
147
                    break;
148
149
                default:
150
                    $message = __('Unknown upload error.');
151
            }
152
153
            return $message;
154
        }
155
    }
156
157
    /**
158
     * Move the temporary source file to the destination file.
159
     *
160
     * @param \Cake\ORM\Entity $entity      The entity that is going to be saved.
161
     * @param bool|string      $source      The temporary source file to copy.
162
     * @param bool|string      $destination The destination file to copy.
163
     * @param bool|string      $field       The current field to process.
164
     * @param array            $options     The configuration options defined by the user.
165
     *
166
     * @return bool
167
     */
168
    protected function _moveFile(Entity $entity, $source = false, $destination = false, $field = false, array $options = [])
169
    {
170
        if ($source === false || $destination === false || $field === false) {
171
            return false;
172
        }
173
174
        if (isset($options['overwrite']) && is_bool($options['overwrite'])) {
175
            $this->_overwrite = $options['overwrite'];
176
        }
177
178
        if ($this->_overwrite) {
179
            $this->_deleteOldUpload($entity, $field, $destination, $options);
180
        }
181
182
        $file = new File($source, false, 0755);
0 ignored issues
show
Bug introduced by
It seems like $source defined by parameter $source on line 168 can also be of type boolean; however, Cake\Filesystem\File::__construct() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
183
184
        if ($file->copy($this->_config['root'] . $destination, $this->_overwrite)) {
185
            return true;
186
        }
187
188
        return false;
189
    }
190
191
    /**
192
     * Delete the old upload file before to save the new file.
193
     *
194
     * We can not just rely on the copy file with the overwrite, because if you use
195
     * an identifier like :md5 (Who use a different name for each file), the copy
196
     * function will not delete the old file.
197
     *
198
     * @param \Cake\ORM\Entity $entity  The entity that is going to be saved.
199
     * @param bool|string      $field   The current field to process.
200
     * @param bool|string      $newFile The new file path.
201
     * @param array            $options The configuration options defined by the user.
202
     *
203
     * @return bool
204
     */
205
    protected function _deleteOldUpload(Entity $entity, $field = false, $newFile = false, array $options = [])
206
    {
207
        if ($field === false || $newFile === false) {
208
            return true;
209
        }
210
211
        $fileInfo = pathinfo($entity->$field);
212
        $newFileInfo = pathinfo($newFile);
213
214 View Code Duplication
        if (isset($options['defaultFile']) && (is_bool($options['defaultFile']) || is_string($options['defaultFile']))) {
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...
215
            $this->_defaultFile = $options['defaultFile'];
216
        }
217
218
        if ($fileInfo['basename'] == $newFileInfo['basename'] ||
219
            $fileInfo['basename'] == pathinfo($this->_defaultFile)['basename']) {
220
            return true;
221
        }
222
223
        if ($this->_prefix) {
224
            $entity->$field = str_replace($this->_prefix, "", $entity->$field);
225
        }
226
227
        $file = new File($this->_config['root'] . $entity->$field, false);
228
229
        if ($file->exists()) {
230
            $file->delete();
231
            return true;
232
        }
233
234
        return false;
235
    }
236
237
    /**
238
     * Get the path formatted without its identifiers to upload the file.
239
     *
240
     * Identifiers :
241
     *      :id  : Id of the Entity.
242
     *      :md5 : A random and unique identifier with 32 characters.
243
     *      :y   : Based on the current year.
244
     *      :m   : Based on the current month.
245
     *
246
     * i.e : upload/:id/:md5 -> upload/2/5e3e0d0f163196cb9526d97be1b2ce26.jpg
247
     *
248
     * @param \Cake\ORM\Entity $entity    The entity that is going to be saved.
249
     * @param bool|string      $path      The path to upload the file with its identifiers.
250
     * @param bool|string      $extension The extension of the file.
251
     *
252
     * @return bool|string
253
     */
254
    protected function _getUploadPath(Entity $entity, $path = false, $extension = false)
255
    {
256
        if ($extension === false || $path === false) {
257
            return false;
258
        }
259
260
        $path = trim($path, DS);
261
262
        $identifiers = [
263
            ':id' => $entity->id,
264
            ':md5' => md5(rand() . uniqid() . time()),
265
            ':y' => date('Y'),
266
            ':m' => date('m')
267
        ];
268
269
        return strtr($path, $identifiers) . '.' . strtolower($extension);
270
    }
271
}
272