FileValidator.php 8.26 KB
Newer Older
w  
Qiang Xue committed
1 2 3 4 5
<?php
/**
 * CFileValidator class file.
 *
 * @link http://www.yiiframework.com/
Qiang Xue committed
6
 * @copyright Copyright &copy; 2008 Yii Software LLC
w  
Qiang Xue committed
7 8 9
 * @license http://www.yiiframework.com/license/
 */

w  
Qiang Xue committed
10 11
namespace yii\validators;

w  
Qiang Xue committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
/**
 * CFileValidator verifies if an attribute is receiving a valid uploaded file.
 *
 * It uses the model class and attribute name to retrieve the information
 * about the uploaded file. It then checks if a file is uploaded successfully,
 * if the file size is within the limit and if the file type is allowed.
 *
 * This validator will attempt to fetch uploaded data if attribute is not
 * previously set. Please note that this cannot be done if input is tabular:
 * <pre>
 *  foreach($models as $i=>$model)
 *     $model->attribute = CUploadedFile::getInstance($model, "[$i]attribute");
 * </pre>
 * Please note that you must use {@link CUploadedFile::getInstances} for multiple
 * file uploads.
 *
 * When using CFileValidator with an active record, the following code is often used:
 * <pre>
 *  if($model->save())
 *  {
 *     // single upload
 *     $model->attribute->saveAs($path);
 *     // multiple upload
 *     foreach($model->attribute as $file)
 *        $file->saveAs($path);
 *  }
 * </pre>
 *
 * You can use {@link CFileValidator} to validate the file attribute.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
Alexander Makarov committed
43
 * @since 2.0
w  
Qiang Xue committed
44
 */
w  
Qiang Xue committed
45
class CFileValidator extends Validator
w  
Qiang Xue committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
{
	/**
	 * @var boolean whether the attribute requires a file to be uploaded or not.
	 * Defaults to false, meaning a file is required to be uploaded.
	 */
	public $allowEmpty = false;
	/**
	 * @var mixed a list of file name extensions that are allowed to be uploaded.
	 * This can be either an array or a string consisting of file extension names
	 * separated by space or comma (e.g. "gif, jpg").
	 * Extension names are case-insensitive. Defaults to null, meaning all file name
	 * extensions are allowed.
	 */
	public $types;
	/**
	 * @var integer the minimum number of bytes required for the uploaded file.
	 * Defaults to null, meaning no limit.
	 * @see tooSmall
	 */
	public $minSize;
	/**
	 * @var integer the maximum number of bytes required for the uploaded file.
	 * Defaults to null, meaning no limit.
	 * Note, the size limit is also affected by 'upload_max_filesize' INI setting
	 * and the 'MAX_FILE_SIZE' hidden field value.
	 * @see tooLarge
	 */
	public $maxSize;
	/**
	 * @var string the error message used when the uploaded file is too large.
	 * @see maxSize
	 */
	public $tooLarge;
	/**
	 * @var string the error message used when the uploaded file is too small.
	 * @see minSize
	 */
	public $tooSmall;
	/**
	 * @var string the error message used when the uploaded file has an extension name
	 * that is not listed among {@link extensions}.
	 */
	public $wrongType;
	/**
	 * @var integer the maximum file count the given attribute can hold.
	 * It defaults to 1, meaning single file upload. By defining a higher number,
	 * multiple uploads become possible.
	 */
	public $maxFiles = 1;
	/**
	 * @var string the error message used if the count of multiple uploads exceeds
	 * limit.
	 */
	public $tooMany;

	/**
	 * Set the attribute and then validates using {@link validateFile}.
	 * If there is any error, the error message is added to the object.
w  
Qiang Xue committed
104
	 * @param \yii\base\Model $object the object being validated
w  
Qiang Xue committed
105 106
	 * @param string $attribute the attribute being validated
	 */
w  
Qiang Xue committed
107
	public function validateAttribute($object, $attribute)
w  
Qiang Xue committed
108 109 110 111 112 113 114 115 116 117
	{
		if ($this->maxFiles > 1)
		{
			$files = $object->$attribute;
			if (!is_array($files) || !isset($files[0]) || !$files[0] instanceof CUploadedFile)
				$files = CUploadedFile::getInstances($object, $attribute);
			if (array() === $files)
				return $this->emptyAttribute($object, $attribute);
			if (count($files) > $this->maxFiles)
			{
118
				$message = $this->tooMany !== null ? $this->tooMany : \Yii::t('yii|{attribute} cannot accept more than {limit} files.');
w  
Qiang Xue committed
119
				$this->addError($object, $attribute, $message, array('{attribute}' => $attribute, '{limit}' => $this->maxFiles));
Qiang Xue committed
120
			} else
w  
Qiang Xue committed
121 122
				foreach ($files as $file)
					$this->validateFile($object, $attribute, $file);
Qiang Xue committed
123
		} else
w  
Qiang Xue committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137
		{
			$file = $object->$attribute;
			if (!$file instanceof CUploadedFile)
			{
				$file = CUploadedFile::getInstance($object, $attribute);
				if (null === $file)
					return $this->emptyAttribute($object, $attribute);
			}
			$this->validateFile($object, $attribute, $file);
		}
	}

	/**
	 * Internally validates a file object.
w  
Qiang Xue committed
138
	 * @param \yii\base\Model $object the object being validated
w  
Qiang Xue committed
139 140 141
	 * @param string $attribute the attribute being validated
	 * @param CUploadedFile $file uploaded file passed to check against a set of rules
	 */
w  
Qiang Xue committed
142
	public function validateFile($object, $attribute, $file)
w  
Qiang Xue committed
143 144 145 146 147
	{
		if (null === $file || ($error = $file->getError()) == UPLOAD_ERR_NO_FILE)
			return $this->emptyAttribute($object, $attribute);
		elseif ($error == UPLOAD_ERR_INI_SIZE || $error == UPLOAD_ERR_FORM_SIZE || $this->maxSize !== null && $file->getSize() > $this->maxSize)
		{
148
			$message = $this->tooLarge !== null ? $this->tooLarge : \Yii::t('yii|The file "{file}" is too large. Its size cannot exceed {limit} bytes.');
w  
Qiang Xue committed
149
			$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit()));
Qiang Xue committed
150
		} elseif ($error == UPLOAD_ERR_PARTIAL)
151
			throw new CException(\Yii::t('yii|The file "{file}" was only partially uploaded.', array('{file}' => $file->getName())));
w  
Qiang Xue committed
152
		elseif ($error == UPLOAD_ERR_NO_TMP_DIR)
153
			throw new CException(\Yii::t('yii|Missing the temporary folder to store the uploaded file "{file}".', array('{file}' => $file->getName())));
w  
Qiang Xue committed
154
		elseif ($error == UPLOAD_ERR_CANT_WRITE)
155
			throw new CException(\Yii::t('yii|Failed to write the uploaded file "{file}" to disk.', array('{file}' => $file->getName())));
w  
Qiang Xue committed
156
		elseif (defined('UPLOAD_ERR_EXTENSION') && $error == UPLOAD_ERR_EXTENSION)  // available for PHP 5.2.0 or above
157
			throw new CException(\Yii::t('yii|File upload was stopped by extension.'));
w  
Qiang Xue committed
158 159 160

		if ($this->minSize !== null && $file->getSize() < $this->minSize)
		{
161
			$message = $this->tooSmall !== null ? $this->tooSmall : \Yii::t('yii|The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.');
w  
Qiang Xue committed
162 163 164 165 166 167 168 169 170 171 172
			$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{limit}' => $this->minSize));
		}

		if ($this->types !== null)
		{
			if (is_string($this->types))
				$types = preg_split('/[\s,]+/', strtolower($this->types), -1, PREG_SPLIT_NO_EMPTY);
			else
				$types = $this->types;
			if (!in_array(strtolower($file->getExtensionName()), $types))
			{
173
				$message = $this->wrongType !== null ? $this->wrongType : \Yii::t('yii|The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.');
w  
Qiang Xue committed
174 175 176 177 178 179 180
				$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{extensions}' => implode(', ', $types)));
			}
		}
	}

	/**
	 * Raises an error to inform end user about blank attribute.
w  
Qiang Xue committed
181
	 * @param \yii\base\Model $object the object being validated
w  
Qiang Xue committed
182 183
	 * @param string $attribute the attribute being validated
	 */
w  
Qiang Xue committed
184
	public function emptyAttribute($object, $attribute)
w  
Qiang Xue committed
185 186 187
	{
		if (!$this->allowEmpty)
		{
188
			$message = $this->message !== null ? $this->message : \Yii::t('yii|{attribute} cannot be blank.');
w  
Qiang Xue committed
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
			$this->addError($object, $attribute, $message);
		}
	}

	/**
	 * Returns the maximum size allowed for uploaded files.
	 * This is determined based on three factors:
	 * <ul>
	 * <li>'upload_max_filesize' in php.ini</li>
	 * <li>'MAX_FILE_SIZE' hidden field</li>
	 * <li>{@link maxSize}</li>
	 * </ul>
	 *
	 * @return integer the size limit for uploaded files.
	 */
w  
Qiang Xue committed
204
	public function getSizeLimit()
w  
Qiang Xue committed
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
	{
		$limit = ini_get('upload_max_filesize');
		$limit = $this->sizeToBytes($limit);
		if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit)
			$limit = $this->maxSize;
		if (isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $limit)
			$limit = $_POST['MAX_FILE_SIZE'];
		return $limit;
	}

	/**
	 * Converts php.ini style size to bytes
	 *
	 * @param string $sizeStr $sizeStr
	 * @return int
	 */
	private function sizeToBytes($sizeStr)
	{
		switch (substr($sizeStr, -1))
		{
			case 'M': case 'm': return (int)$sizeStr * 1048576;
			case 'K': case 'k': return (int)$sizeStr * 1024;
			case 'G': case 'g': return (int)$sizeStr * 1073741824;
			default: return (int)$sizeStr;
		}
	}
}