FileValidator.php 8.22 KB
Newer Older
w  
Qiang Xue committed
1 2 3
<?php
/**
 * @link http://www.yiiframework.com/
Qiang Xue committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
w  
Qiang Xue committed
5 6 7
 * @license http://www.yiiframework.com/license/
 */

w  
Qiang Xue committed
8 9
namespace yii\validators;

w  
Qiang Xue committed
10 11 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
/**
 * 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
41
 * @since 2.0
w  
Qiang Xue committed
42
 */
w  
Qiang Xue committed
43
class CFileValidator extends Validator
w  
Qiang Xue committed
44 45 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
{
	/**
	 * @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
102
	 * @param \yii\base\Model $object the object being validated
w  
Qiang Xue committed
103 104
	 * @param string $attribute the attribute being validated
	 */
w  
Qiang Xue committed
105
	public function validateAttribute($object, $attribute)
w  
Qiang Xue committed
106 107 108 109 110 111 112 113 114 115
	{
		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)
			{
116
				$message = $this->tooMany !== null ? $this->tooMany : \Yii::t('yii|{attribute} cannot accept more than {limit} files.');
w  
Qiang Xue committed
117
				$this->addError($object, $attribute, $message, array('{attribute}' => $attribute, '{limit}' => $this->maxFiles));
Qiang Xue committed
118
			} else
w  
Qiang Xue committed
119 120
				foreach ($files as $file)
					$this->validateFile($object, $attribute, $file);
Qiang Xue committed
121
		} else
w  
Qiang Xue committed
122 123 124 125 126 127 128 129 130 131 132 133 134 135
		{
			$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
136
	 * @param \yii\base\Model $object the object being validated
w  
Qiang Xue committed
137 138 139
	 * @param string $attribute the attribute being validated
	 * @param CUploadedFile $file uploaded file passed to check against a set of rules
	 */
w  
Qiang Xue committed
140
	public function validateFile($object, $attribute, $file)
w  
Qiang Xue committed
141 142 143 144 145
	{
		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)
		{
146
			$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
147
			$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit()));
Qiang Xue committed
148
		} elseif ($error == UPLOAD_ERR_PARTIAL)
149
			throw new CException(\Yii::t('yii|The file "{file}" was only partially uploaded.', array('{file}' => $file->getName())));
w  
Qiang Xue committed
150
		elseif ($error == UPLOAD_ERR_NO_TMP_DIR)
151
			throw new CException(\Yii::t('yii|Missing the temporary folder to store the uploaded file "{file}".', array('{file}' => $file->getName())));
w  
Qiang Xue committed
152
		elseif ($error == UPLOAD_ERR_CANT_WRITE)
153
			throw new CException(\Yii::t('yii|Failed to write the uploaded file "{file}" to disk.', array('{file}' => $file->getName())));
w  
Qiang Xue committed
154
		elseif (defined('UPLOAD_ERR_EXTENSION') && $error == UPLOAD_ERR_EXTENSION)  // available for PHP 5.2.0 or above
155
			throw new CException(\Yii::t('yii|File upload was stopped by extension.'));
w  
Qiang Xue committed
156 157 158

		if ($this->minSize !== null && $file->getSize() < $this->minSize)
		{
159
			$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
160 161 162 163 164 165 166 167 168 169 170
			$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))
			{
171
				$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
172 173 174 175 176 177 178
				$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
179
	 * @param \yii\base\Model $object the object being validated
w  
Qiang Xue committed
180 181
	 * @param string $attribute the attribute being validated
	 */
w  
Qiang Xue committed
182
	public function emptyAttribute($object, $attribute)
w  
Qiang Xue committed
183 184 185
	{
		if (!$this->allowEmpty)
		{
186
			$message = $this->message !== null ? $this->message : \Yii::t('yii|{attribute} cannot be blank.');
w  
Qiang Xue committed
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
			$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
202
	public function getSizeLimit()
w  
Qiang Xue committed
203 204 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
	{
		$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;
		}
	}
}