Issues (910)

framework/web/UploadedFile.php (1 issue)

1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\BaseObject;
12
use yii\helpers\ArrayHelper;
13
use yii\helpers\Html;
14
15
/**
16
 * UploadedFile represents the information for an uploaded file.
17
 *
18
 * You can call [[getInstance()]] to retrieve the instance of an uploaded file,
19
 * and then use [[saveAs()]] to save it on the server.
20
 * You may also query other information about the file, including [[name]],
21
 * [[tempName]], [[type]], [[size]], [[error]] and [[fullPath]].
22
 *
23
 * For more details and usage information on UploadedFile, see the [guide article on handling uploads](guide:input-file-upload).
24
 *
25
 * @property-read string $baseName Original file base name.
26
 * @property-read string $extension File extension.
27
 * @property-read bool $hasError Whether there is an error with the uploaded file. Check [[error]] for
28
 * detailed error code information.
29
 *
30
 * @author Qiang Xue <[email protected]>
31
 * @since 2.0
32
 */
33
class UploadedFile extends BaseObject
34
{
35
    /**
36
     * @var string the original name of the file being uploaded
37
     */
38
    public $name;
39
    /**
40
     * @var string the path of the uploaded file on the server.
41
     * Note, this is a temporary file which will be automatically deleted by PHP
42
     * after the current request is processed.
43
     */
44
    public $tempName;
45
    /**
46
     * @var string the MIME-type of the uploaded file (such as "image/gif").
47
     * Since this MIME type is not checked on the server-side, do not take this value for granted.
48
     * Instead, use [[\yii\helpers\FileHelper::getMimeType()]] to determine the exact MIME type.
49
     */
50
    public $type;
51
    /**
52
     * @var int the actual size of the uploaded file in bytes
53
     */
54
    public $size;
55
    /**
56
     * @var int an error code describing the status of this file uploading.
57
     * @see https://www.php.net/manual/en/features.file-upload.errors.php
58
     */
59
    public $error;
60
    /**
61
     * @var string|null The full path as submitted by the browser. Note this value does not always
62
     * contain a real directory structure, and cannot be trusted. Available as of PHP 8.1.
63
     * @since 2.0.46
64
     */
65
    public $fullPath;
66
67
    /**
68
     * @var resource|null a temporary uploaded stream resource used within PUT and PATCH request.
69
     */
70
    private $_tempResource;
71
    /**
72
     * @var array[]
73
     */
74
    private static $_files;
75
76
77
    /**
78
     * UploadedFile constructor.
79
     *
80
     * @param array $config name-value pairs that will be used to initialize the object properties
81
     */
82 53
    public function __construct($config = [])
83
    {
84 53
        $this->_tempResource = ArrayHelper::remove($config, 'tempResource');
85 53
        parent::__construct($config);
86
    }
87
88
    /**
89
     * String output.
90
     * This is PHP magic method that returns string representation of an object.
91
     * The implementation here returns the uploaded file's name.
92
     * @return string the string representation of the object
93
     */
94
    public function __toString()
95
    {
96
        return $this->name;
97
    }
98
99
    /**
100
     * Returns an uploaded file for the given model attribute.
101
     * The file should be uploaded using [[\yii\widgets\ActiveField::fileInput()]].
102
     * @param \yii\base\Model $model the data model
103
     * @param string $attribute the attribute name. The attribute name may contain array indexes.
104
     * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array.
105
     * @return UploadedFile|null the instance of the uploaded file.
106
     * Null is returned if no file is uploaded for the specified model attribute.
107
     * @see getInstanceByName()
108
     */
109 2
    public static function getInstance($model, $attribute)
110
    {
111 2
        $name = Html::getInputName($model, $attribute);
112 2
        return static::getInstanceByName($name);
113
    }
114
115
    /**
116
     * Returns all uploaded files for the given model attribute.
117
     * @param \yii\base\Model $model the data model
118
     * @param string $attribute the attribute name. The attribute name may contain array indexes
119
     * for tabular file uploading, e.g. '[1]file'.
120
     * @return UploadedFile[] array of UploadedFile objects.
121
     * Empty array is returned if no available file was found for the given attribute.
122
     */
123 1
    public static function getInstances($model, $attribute)
124
    {
125 1
        $name = Html::getInputName($model, $attribute);
126 1
        return static::getInstancesByName($name);
127
    }
128
129
    /**
130
     * Returns an uploaded file according to the given file input name.
131
     * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]').
132
     * @param string $name the name of the file input field.
133
     * @return UploadedFile|null the instance of the uploaded file.
134
     * Null is returned if no file is uploaded for the specified name.
135
     */
136 2
    public static function getInstanceByName($name)
137
    {
138 2
        $files = self::loadFiles();
139 2
        return isset($files[$name]) ? new static($files[$name]) : null;
140
    }
141
142
    /**
143
     * Returns an array of uploaded files corresponding to the specified file input name.
144
     * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]',
145
     * 'files[n]'..., and you can retrieve them all by passing 'files' as the name.
146
     * @param string $name the name of the array of files
147
     * @return UploadedFile[] the array of UploadedFile objects. Empty array is returned
148
     * if no adequate upload was found. Please note that this array will contain
149
     * all files from all sub-arrays regardless how deeply nested they are.
150
     */
151 1
    public static function getInstancesByName($name)
152
    {
153 1
        $files = self::loadFiles();
154 1
        if (isset($files[$name])) {
155
            return [new static($files[$name])];
156
        }
157 1
        $results = [];
158 1
        foreach ($files as $key => $file) {
159 1
            if (strpos($key, "{$name}[") === 0) {
160 1
                $results[] = new static($file);
161
            }
162
        }
163
164 1
        return $results;
165
    }
166
167
    /**
168
     * Cleans up the loaded UploadedFile instances.
169
     * This method is mainly used by test scripts to set up a fixture.
170
     */
171 1
    public static function reset()
172
    {
173 1
        self::$_files = null;
174
    }
175
176
    /**
177
     * Saves the uploaded file.
178
     * If the target file `$file` already exists, it will be overwritten.
179
     * @param string $file the file path or a path alias used to save the uploaded file.
180
     * @param bool $deleteTempFile whether to delete the temporary file after saving.
181
     * If true, you will not be able to save the uploaded file again in the current request.
182
     * @return bool true whether the file is saved successfully
183
     * @see error
184
     */
185 1
    public function saveAs($file, $deleteTempFile = true)
186
    {
187 1
        if ($this->hasError) {
188
            return false;
189
        }
190
191 1
        $targetFile = Yii::getAlias($file);
192 1
        if (is_resource($this->_tempResource)) {
193 1
            $result = $this->copyTempFile($targetFile);
194 1
            return $deleteTempFile ? @fclose($this->_tempResource) : (bool) $result;
195
        }
196
197 1
        return $deleteTempFile ? move_uploaded_file($this->tempName, $targetFile) : copy($this->tempName, $targetFile);
198
    }
199
200
    /**
201
     * Copy temporary file into file specified
202
     *
203
     * @param string $targetFile path of the file to copy to
204
     * @return int|false the total count of bytes copied, or false on failure
205
     * @since 2.0.32
206
     */
207 1
    protected function copyTempFile($targetFile)
208
    {
209 1
        $target = fopen($targetFile, 'wb');
210 1
        if ($target === false) {
211
            return false;
212
        }
213
214 1
        $result = stream_copy_to_stream($this->_tempResource, $target);
215 1
        @fclose($target);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for fclose(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

215
        /** @scrutinizer ignore-unhandled */ @fclose($target);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
216
217 1
        return $result;
218
    }
219
220
    /**
221
     * @return string original file base name
222
     */
223 1
    public function getBaseName()
224
    {
225
        // https://github.com/yiisoft/yii2/issues/11012
226 1
        $pathInfo = pathinfo('_' . $this->name, PATHINFO_FILENAME);
227 1
        return mb_substr($pathInfo, 1, mb_strlen($pathInfo, '8bit'), '8bit');
228
    }
229
230
    /**
231
     * @return string file extension
232
     */
233 17
    public function getExtension()
234
    {
235 17
        return strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
236
    }
237
238
    /**
239
     * @return bool whether there is an error with the uploaded file.
240
     * Check [[error]] for detailed error code information.
241
     */
242 1
    public function getHasError()
243
    {
244 1
        return $this->error != UPLOAD_ERR_OK;
245
    }
246
247
    /**
248
     * Returns reformated data of uplodaded files.
249
     *
250
     * @return array[]
251
     */
252 3
    private static function loadFiles()
253
    {
254 3
        if (self::$_files === null) {
255 2
            self::$_files = [];
256 2
            if (isset($_FILES) && is_array($_FILES)) {
257 2
                foreach ($_FILES as $key => $info) {
258 2
                    self::loadFilesRecursive(
259 2
                        $key,
260 2
                        $info['name'],
261 2
                        $info['tmp_name'],
262 2
                        $info['type'],
263 2
                        $info['size'],
264 2
                        $info['error'],
265 2
                        isset($info['full_path']) ? $info['full_path'] : [],
266 2
                        isset($info['tmp_resource']) ? $info['tmp_resource'] : []
267 2
                    );
268
                }
269
            }
270
        }
271
272 3
        return self::$_files;
273
    }
274
275
    /**
276
     * Recursive reformats data of uplodaded file(s).
277
     *
278
     * @param string $key key for identifying uploaded file(sub-array index)
279
     * @param string[]|string $names file name(s) provided by PHP
280
     * @param string[]|string $tempNames temporary file name(s) provided by PHP
281
     * @param string[]|string $types file type(s) provided by PHP
282
     * @param int[]|int $sizes file size(s) provided by PHP
283
     * @param int[]|int $errors uploading issue(s) provided by PHP
284
     * @param array|string|null $fullPaths the full path(s) as submitted by the browser/PHP
285
     * @param array|resource|null $tempResources the resource(s)
286
     */
287 2
    private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors, $fullPaths, $tempResources)
288
    {
289 2
        if (is_array($names)) {
290 1
            foreach ($names as $i => $name) {
291 1
                self::loadFilesRecursive(
292 1
                    $key . '[' . $i . ']',
293 1
                    $name,
294 1
                    $tempNames[$i],
295 1
                    $types[$i],
296 1
                    $sizes[$i],
297 1
                    $errors[$i],
298 1
                    isset($fullPaths[$i]) ? $fullPaths[$i] : null,
299 1
                    isset($tempResources[$i]) ? $tempResources[$i] : null
300 1
                );
301
            }
302 2
        } elseif ($errors != UPLOAD_ERR_NO_FILE) {
303 2
            self::$_files[$key] = [
304 2
                'name' => $names,
305 2
                'tempName' => $tempNames,
306 2
                'tempResource' => is_resource($tempResources) ? $tempResources : null,
307 2
                'type' => $types,
308 2
                'size' => $sizes,
309 2
                'error' => $errors,
310 2
                'fullPath' => is_string($fullPaths) ? $fullPaths : null,
311 2
            ];
312
        }
313
    }
314
}
315