Passed
Push — fix-tests ( 6dd538...727178 )
by Alexander
195:41 queued 192:18
created

UploadedFile::saveAs()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 5.0488

Importance

Changes 0
Metric Value
cc 5
eloc 7
nc 5
nop 2
dl 0
loc 13
ccs 7
cts 8
cp 0.875
crap 5.0488
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://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]] and [[error]].
22
 *
23
 * For more details and usage information on UploadedFile, see the [guide article on handling uploads](guide:input-file-upload).
24
 *
25
 * @property string $baseName Original file base name. This property is read-only.
26
 * @property string $extension File extension. This property is read-only.
27
 * @property bool $hasError Whether there is an error with the uploaded file. Check [[error]] for detailed
28
 * error code information. This property is read-only.
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://secure.php.net/manual/en/features.file-upload.errors.php
58
     */
59
    public $error;
60
61
    /**
62
     * @var resource a temporary uploaded stream resource used within PUT and PATCH request.
63
     */
64
    private $_tempResource;
65
    private static $_files;
66
67
68
    /**
69
     * UploadedFile constructor.
70
     *
71
     * @param array $config name-value pairs that will be used to initialize the object properties
72
     */
73 42
    public function __construct($config = [])
74
    {
75 42
        $this->_tempResource = ArrayHelper::remove($config, 'tempResource');
76 42
        parent::__construct($config);
77 42
    }
78
79
    /**
80
     * String output.
81
     * This is PHP magic method that returns string representation of an object.
82
     * The implementation here returns the uploaded file's name.
83
     * @return string the string representation of the object
84
     */
85
    public function __toString()
86
    {
87
        return $this->name;
88
    }
89
90
    /**
91
     * Returns an uploaded file for the given model attribute.
92
     * The file should be uploaded using [[\yii\widgets\ActiveField::fileInput()]].
93
     * @param \yii\base\Model $model the data model
94
     * @param string $attribute the attribute name. The attribute name may contain array indexes.
95
     * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array.
96
     * @return null|UploadedFile the instance of the uploaded file.
97
     * Null is returned if no file is uploaded for the specified model attribute.
98
     * @see getInstanceByName()
99
     */
100 2
    public static function getInstance($model, $attribute)
101
    {
102 2
        $name = Html::getInputName($model, $attribute);
103 2
        return static::getInstanceByName($name);
104
    }
105
106
    /**
107
     * Returns all uploaded files for the given model attribute.
108
     * @param \yii\base\Model $model the data model
109
     * @param string $attribute the attribute name. The attribute name may contain array indexes
110
     * for tabular file uploading, e.g. '[1]file'.
111
     * @return UploadedFile[] array of UploadedFile objects.
112
     * Empty array is returned if no available file was found for the given attribute.
113
     */
114 1
    public static function getInstances($model, $attribute)
115
    {
116 1
        $name = Html::getInputName($model, $attribute);
117 1
        return static::getInstancesByName($name);
118
    }
119
120
    /**
121
     * Returns an uploaded file according to the given file input name.
122
     * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]').
123
     * @param string $name the name of the file input field.
124
     * @return null|UploadedFile the instance of the uploaded file.
125
     * Null is returned if no file is uploaded for the specified name.
126
     */
127 2
    public static function getInstanceByName($name)
128
    {
129 2
        $files = self::loadFiles();
130 2
        return isset($files[$name]) ? new static($files[$name]) : null;
131
    }
132
133
    /**
134
     * Returns an array of uploaded files corresponding to the specified file input name.
135
     * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]',
136
     * 'files[n]'..., and you can retrieve them all by passing 'files' as the name.
137
     * @param string $name the name of the array of files
138
     * @return UploadedFile[] the array of UploadedFile objects. Empty array is returned
139
     * if no adequate upload was found. Please note that this array will contain
140
     * all files from all sub-arrays regardless how deeply nested they are.
141
     */
142 1
    public static function getInstancesByName($name)
143
    {
144 1
        $files = self::loadFiles();
145 1
        if (isset($files[$name])) {
146
            return [new static($files[$name])];
147
        }
148 1
        $results = [];
149 1
        foreach ($files as $key => $file) {
150 1
            if (strpos($key, "{$name}[") === 0) {
151 1
                $results[] = new static($file);
152
            }
153
        }
154
155 1
        return $results;
156
    }
157
158
    /**
159
     * Cleans up the loaded UploadedFile instances.
160
     * This method is mainly used by test scripts to set up a fixture.
161
     */
162 1
    public static function reset()
163
    {
164 1
        self::$_files = null;
165 1
    }
166
167
    /**
168
     * Saves the uploaded file.
169
     * If the target file `$file` already exists, it will be overwritten.
170
     * @param string $file the file path or a path alias used to save the uploaded file.
171
     * @param bool $deleteTempFile whether to delete the temporary file after saving.
172
     * If true, you will not be able to save the uploaded file again in the current request.
173
     * @return bool true whether the file is saved successfully
174
     * @see error
175
     */
176 1
    public function saveAs($file, $deleteTempFile = true)
177
    {
178 1
        if ($this->hasError) {
179
            return false;
180
        }
181
182 1
        $targetFile = Yii::getAlias($file);
183 1
        if (is_resource($this->_tempResource)) {
184 1
            $result = $this->copyTempFile($targetFile);
185 1
            return $deleteTempFile ? @fclose($this->_tempResource) : (bool) $result;
186
        }
187
188 1
        return $deleteTempFile ? move_uploaded_file($this->tempName, $targetFile) : copy($this->tempName, $targetFile);
189
    }
190
191
    /**
192
     * Copy temporary file into file specified
193
     *
194
     * @param string $targetFile path of the file to copy to
195
     * @return bool|int the total count of bytes copied, or false on failure
196
     * @since 2.0.32
197
     */
198 1
    protected function copyTempFile($targetFile)
199
    {
200 1
        $target = fopen($targetFile, 'wb');
201 1
        if ($target === false) {
202
            return false;
203
        }
204
205 1
        $result = stream_copy_to_stream($this->_tempResource, $target);
206 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

206
        /** @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...
207
208 1
        return $result;
209
    }
210
211
    /**
212
     * @return string original file base name
213
     */
214 1
    public function getBaseName()
215
    {
216
        // https://github.com/yiisoft/yii2/issues/11012
217 1
        $pathInfo = pathinfo('_' . $this->name, PATHINFO_FILENAME);
218 1
        return mb_substr($pathInfo, 1, mb_strlen($pathInfo, '8bit'), '8bit');
219
    }
220
221
    /**
222
     * @return string file extension
223
     */
224 14
    public function getExtension()
225
    {
226 14
        return strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
227
    }
228
229
    /**
230
     * @return bool whether there is an error with the uploaded file.
231
     * Check [[error]] for detailed error code information.
232
     */
233 1
    public function getHasError()
234
    {
235 1
        return $this->error != UPLOAD_ERR_OK;
236
    }
237
238
    /**
239
     * Creates UploadedFile instances from $_FILE.
240
     * @return array the UploadedFile instances
241
     */
242 3
    private static function loadFiles()
243
    {
244 3
        if (self::$_files === null) {
245 2
            self::$_files = [];
246 2
            if (isset($_FILES) && is_array($_FILES)) {
247 2
                foreach ($_FILES as $class => $info) {
248 2
                    $resource = isset($info['tmp_resource']) ? $info['tmp_resource'] : [];
249 2
                    self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error'], $resource);
250
                }
251
            }
252
        }
253
254 3
        return self::$_files;
255
    }
256
257
    /**
258
     * Creates UploadedFile instances from $_FILE recursively.
259
     * @param string $key key for identifying uploaded file: class name and sub-array indexes
260
     * @param mixed $names file names provided by PHP
261
     * @param mixed $tempNames temporary file names provided by PHP
262
     * @param mixed $types file types provided by PHP
263
     * @param mixed $sizes file sizes provided by PHP
264
     * @param mixed $errors uploading issues provided by PHP
265
     */
266 2
    private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors, $tempResources)
267
    {
268 2
        if (is_array($names)) {
269 1
            foreach ($names as $i => $name) {
270 1
                $resource = isset($tempResources[$i]) ? $tempResources[$i] : [];
271 1
                self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i], $resource);
272
            }
273 2
        } elseif ((int) $errors !== UPLOAD_ERR_NO_FILE) {
274 2
            self::$_files[$key] = [
275 2
                'name' => $names,
276 2
                'tempName' => $tempNames,
277 2
                'tempResource' => $tempResources,
278 2
                'type' => $types,
279 2
                'size' => $sizes,
280 2
                'error' => $errors,
281
            ];
282
        }
283 2
    }
284
}
285