Upload::save()   B
last analyzed

Complexity

Conditions 7
Paths 5

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 16
rs 8.8333
c 0
b 0
f 0
cc 7
nc 5
nop 1
1
<?php
2
/**
3
 * KumbiaPHP web & app Framework
4
 *
5
 * LICENSE
6
 *
7
 * This source file is subject to the new BSD license that is bundled
8
 * with this package in the file LICENSE.
9
 *
10
 * @category   Kumbia
11
 * @package    Upload
12
 *
13
 * @copyright  Copyright (c) 2005 - 2023 KumbiaPHP Team (http://www.kumbiaphp.com)
14
 * @license    https://github.com/KumbiaPHP/KumbiaPHP/blob/master/LICENSE   New BSD License
15
 */
16
17
/**
18
 * Sube archivos al servidor.
19
 *
20
 * @category   Kumbia
21
 * @package    Upload
22
 */
23
abstract class Upload {
24
25
    /**
26
     * Nombre de archivo subido por método POST
27
     *
28
     * @var string
29
     */
30
    protected $_name;
31
32
    /**
33
     * Ruta donde se guardara el archivo
34
     *
35
     * @var string
36
     */
37
    protected $_path;
38
39
    /**
40
     * Permitir subir archivos de scripts ejecutables
41
     *
42
     * @var boolean
43
     */
44
    protected $_allowScripts = FALSE;
45
46
    /**
47
     * Tamaño mínimo del archivo
48
     *
49
     * @var string
50
     */
51
    protected $_minSize = '';
52
53
    /**
54
     * Tamaño máximo del archivo
55
     *
56
     * @var string
57
     */
58
    protected $_maxSize = '';
59
60
    /**
61
     * Tipos de archivo permitidos utilizando mime
62
     *
63
     * @var array
64
     */
65
    protected $_types = array();
66
67
    /**
68
     * Extensión de archivo permitida
69
     *
70
     * @var array
71
     */
72
    protected $_extensions = array();
73
74
    /**
75
     * Permitir sobrescribir ficheros
76
     *
77
     * @var bool Por defecto FALSE
78
     */
79
    protected $_overwrite = FALSE;
80
81
    /**
82
     * Constructor
83
     *
84
     * @param string $name nombre de archivo por método POST
85
     */
86
    public function __construct($name) {
87
        $this->_name = $name;
88
    }
89
90
    /**
91
     * Indica si se permitirá guardar archivos de scripts ejecutables
92
     *
93
     * @param boolean $value
94
     */
95
    public function setAllowScripts($value) {
96
        $this->_allowScripts = $value;
97
    }
98
99
    /**
100
     * Asigna el tamaño mínimo permitido para el archivo
101
     *
102
     * @param string $size
103
     */
104
    public function setMinSize($size) {
105
        $this->_minSize = trim($size);
106
    }
107
108
    /**
109
     * Asigna el tamaño máximo permitido para el archivo
110
     *
111
     * @param string $size
112
     */
113
    public function setMaxSize($size) {
114
        $this->_maxSize = trim($size);
115
    }
116
117
    /**
118
     * Asigna los tipos de archivos permitido (mime)
119
     *
120
     * @param array|string $value lista de tipos de archivos permitidos (mime) si es string separado por |
121
     */
122
    public function setTypes($value) {
123
        if (!is_array($value)) {
124
            $value = explode('|', $value);
125
        }
126
127
        $this->_types = $value;
128
    }
129
130
    /**
131
     * Asigna las extensiones de archivos permitidas
132
     *
133
     * @param array|string $value lista de extensiones para archivos, si es string separado por |
134
     */
135
    public function setExtensions($value) {
136
        if (!is_array($value)) {
137
            $value = explode('|', $value);
138
        }
139
140
        $this->_extensions = $value;
141
    }
142
143
    /**
144
     * Permitir sobrescribir el fichero
145
     *
146
     * @param bool $value
147
     */
148
    public function overwrite($value) {
149
        $this->_overwrite = (bool) $value;
150
    }
151
152
    /**
153
     * Acciones antes de guardar
154
     *
155
     * @param string $name nombre con el que se va a guardar el archivo
156
     * @return  boolean|null
157
     */
158
    protected function _beforeSave($name) {
159
    }
160
161
    /**
162
     * Acciones después de guardar
163
     *
164
     * @param string $name nombre con el que se guardo el archivo
165
     * @return  boolean|null
166
     */
167
    protected function _afterSave($name) {
168
    }
169
170
    /**
171
     * Guarda el archivo subido
172
     *
173
     * @param string $name nombre con el que se guardara el archivo
174
     * @return boolean|string Nombre de archivo generado con la extensión o FALSE si falla
175
     */
176
    public function save($name = '') {
177
        if (!$this->isUploaded()) {
178
            return FALSE;
179
        }
180
        if (!$name) {
181
            $name = $_FILES[$this->_name]['name'];
182
        } else {
183
            $name = $name . $this->_getExtension();
184
        }
185
186
        // Guarda el archivo
187
        if ($this->_beforeSave($name) !== FALSE && $this->_overwrite($name) && $this->_validates() && $this->_saveFile($name)) {
0 ignored issues
show
introduced by
The condition $this->_beforeSave($name) !== FALSE is always false.
Loading history...
Bug introduced by
Are you sure the usage of $this->_beforeSave($name) targeting Upload::_beforeSave() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
188
            $this->_afterSave($name);
189
            return $name;
190
        }
191
        return FALSE;
192
    }
193
194
    /**
195
     * Guarda el archivo con un nombre aleatorio
196
     *
197
     * @return string|false Nombre de archivo generado o FALSE si falla
198
     */
199
    public function saveRandom() {
200
201
        // Genera el nombre de archivo
202
        $name = md5(time());
203
204
        // Guarda el archivo
205
        if ($this->save($name)) {
206
            return $name . $this->_getExtension();
207
        }
208
209
        return FALSE;
210
    }
211
212
    /**
213
     * Verifica si el archivo esta subido en el servidor y listo para guardarse
214
     *
215
     * @return boolean
216
     */
217
    public function isUploaded() {
218
219
        // Verifica si ha ocurrido un error al subir
220
        if ($_FILES[$this->_name]['error'] > 0) {
221
            $error = array(UPLOAD_ERR_INI_SIZE => 'el archivo excede el tamaño máximo (' . ini_get('upload_max_filesize') . 'b) permitido por el servidor', UPLOAD_ERR_FORM_SIZE => 'el archivo excede el tamaño máximo permitido', UPLOAD_ERR_PARTIAL => 'se ha subido el archivo parcialmente', UPLOAD_ERR_NO_FILE => 'no se ha subido ningún archivo', UPLOAD_ERR_NO_TMP_DIR => 'no se encuentra el directorio de archivos temporales', UPLOAD_ERR_CANT_WRITE => 'falló al escribir el archivo en disco', UPLOAD_ERR_EXTENSION => 'una extensión de php ha detenido la subida del archivo');
222
223
            Flash::error('Error: ' . $error[$_FILES[$this->_name]['error']]);
224
            return FALSE;
225
        }
226
        return TRUE;
227
    }
228
229
    /**
230
     * Valida el archivo antes de guardar
231
     *
232
     * @return boolean
233
     */
234
    protected function _validates() {
235
        $validations = array('allowScripts', 'types', 'extensions', 'maxSize', 'minSize');
236
        foreach ($validations as $value) {
237
            $func = "_{$value}";
238
            if ($this->$func && !$this->$func()) {
239
                return FALSE;
240
            }
241
        }
242
        return TRUE;
243
    }
244
245
    /**
246
     * Devuelve la extensión
247
     *
248
     * @return string|null
249
     */
250
    protected function _getExtension() {
251
        if ($ext = pathinfo($_FILES[$this->_name]['name'], PATHINFO_EXTENSION)) {
252
            return '.' . $ext;
0 ignored issues
show
Bug introduced by
Are you sure $ext of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

252
            return '.' . /** @scrutinizer ignore-type */ $ext;
Loading history...
253
        }
254
    }
255
256
    /**
257
     * Valida si puede sobrescribir el archivo
258
     *
259
     * @return boolean
260
     */
261
    protected function _overwrite($name) {
262
        if ($this->_overwrite) {
263
            return TRUE;
264
        }
265
        if (is_file("$this->_path/$name")) {
266
            Flash::error('Error: ya existe este fichero. Y no se permite reescribirlo');
267
            return FALSE;
268
        }
269
        return TRUE;
270
    }
271
272
    /**
273
     * Convierte de tamaño legible por humanos a bytes
274
     *
275
     * @param string $size
276
     * @return int
277
     */
278
    protected function _toBytes($size) {
279
        if (is_int($size) || ctype_digit($size)) {
280
            return (int) $size;
281
        }
282
283
        $tipo = strtolower(substr($size, -1));
284
        $size = (int) $size;
285
286
        switch ($tipo) {
287
            case 'g':
288
289
                //Gigabytes
290
                $size *= 1073741824;
291
                break;
292
293
            case 'm':
294
295
                //Megabytes
296
                $size *= 1048576;
297
                break;
298
299
            case 'k':
300
301
                //Kilobytes
302
                $size *= 1024;
303
                break;
304
305
            default:
306
                $size = -1;
307
                Flash::error('Error: el tamaño debe ser un int para bytes, o un string terminado con K, M o G. Ej: 30k , 2M, 2G');
308
        }
309
310
        return $size;
311
    }
312
313
    /**
314
     * Guardar el archivo en el servidor
315
     *
316
     * @param string $name nombre con el que se guardará el archivo
317
     * @return boolean
318
     */
319
    protected abstract function _saveFile($name);
320
321
    /**
322
     * Obtiene el adaptador para Upload
323
     *
324
     * @param string $name nombre de archivo recibido por POST
325
     * @param string $adapter (file, image, model)
326
     * @return Upload
327
     */
328
    public static function factory($name, $adapter = 'file') {
329
        require_once __DIR__ . "/adapters/{$adapter}_upload.php";
330
        $class = $adapter . 'upload';
331
332
        return new $class($name);
333
    }
334
335
    /**
336
     * @param boolean $cond
337
     */
338
    protected function _cond($cond, $message) {
339
        if ($cond) {
340
            Flash::error("Error: $message");
341
            return FALSE;
342
        }
343
        return TRUE;
344
    }
345
346
    protected function _allowScripts() {
347
        return $this->_cond(
348
            !$this->_allowScripts && preg_match('/\.(php|phtml|php3|php4|js|shtml|pl|py|rb|rhtml)$/i', $_FILES[$this->_name]['name']),
349
            'no esta permitido subir scripts ejecutables'
350
        );
351
    }
352
353
    /**
354
     * Valida que el tipo de archivo
355
     *
356
     * @return boolean
357
     */
358
    protected function _types() {
359
        return $this->_cond(
360
            !in_array($_FILES[$this->_name]['type'], $this->_types),
361
            'el tipo de archivo no es válido'
362
        );
363
    }
364
365
    protected function _extensions() {
366
        return $this->_cond(
367
            !preg_match('/\.(' . implode('|', $this->_extensions) . ')$/i', $_FILES[$this->_name]['name']),
368
            'la extensión del archivo no es válida'
369
        );
370
    }
371
372
    protected function _maxSize() {
373
        return $this->_cond(
374
            $_FILES[$this->_name]['size'] > $this->_toBytes($this->_maxSize),
375
            "no se admiten archivos superiores a $this->_maxSize b"
376
        );
377
    }
378
379
    protected function _minSize() {
380
        return $this->_cond(
381
            $_FILES[$this->_name]['size'] < $this->_toBytes($this->_minSize),
382
            "Error: no se admiten archivos inferiores a $this->_minSize b"
383
        );
384
    }
385
}
386