CFileValidator.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <?php
  2. /**
  3. * CFileValidator class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CFileValidator verifies if an attribute is receiving a valid uploaded file.
  12. *
  13. * It uses the model class and attribute name to retrieve the information
  14. * about the uploaded file. It then checks if a file is uploaded successfully,
  15. * if the file size is within the limit and if the file type is allowed.
  16. *
  17. * This validator will attempt to fetch uploaded data if attribute is not
  18. * previously set. Please note that this cannot be done if input is tabular:
  19. * <pre>
  20. * foreach($models as $i=>$model)
  21. * $model->attribute = CUploadedFile::getInstance($model, "[$i]attribute");
  22. * </pre>
  23. * Please note that you must use {@link CUploadedFile::getInstances} for multiple
  24. * file uploads.
  25. *
  26. * When using CFileValidator with an active record, the following code is often used:
  27. * <pre>
  28. * $model->attribute = CUploadedFile::getInstance($model, "attribute");
  29. * if($model->save())
  30. * {
  31. * // single upload
  32. * $model->attribute->saveAs($path);
  33. * // multiple upload
  34. * foreach($model->attribute as $file)
  35. * $file->saveAs($path);
  36. * }
  37. * </pre>
  38. *
  39. * You can use {@link CFileValidator} to validate the file attribute.
  40. *
  41. * In addition to the {@link message} property for setting a custom error message,
  42. * CFileValidator has a few custom error messages you can set that correspond to different
  43. * validation scenarios. When the file is too large, you may use the {@link tooLarge} property
  44. * to define a custom error message. Similarly for {@link tooSmall}, {@link wrongType} and
  45. * {@link tooMany}. The messages may contain additional placeholders that will be replaced
  46. * with the actual content. In addition to the "{attribute}" placeholder, recognized by all
  47. * validators (see {@link CValidator}), CFileValidator allows for the following placeholders
  48. * to be specified:
  49. * <ul>
  50. * <li>{file}: replaced with the name of the file.</li>
  51. * <li>{limit}: when using {@link tooLarge}, replaced with {@link maxSize};
  52. * when using {@link tooSmall}, replaced with {@link minSize}; and when using {@link tooMany}
  53. * replaced with {@link maxFiles}.</li>
  54. * <li>{extensions}: when using {@link wrongType}, it will be replaced with the allowed extensions.</li>
  55. * </ul>
  56. *
  57. * @author Qiang Xue <qiang.xue@gmail.com>
  58. * @package system.validators
  59. * @since 1.0
  60. */
  61. class CFileValidator extends CValidator
  62. {
  63. /**
  64. * @var boolean whether the attribute requires a file to be uploaded or not.
  65. * Defaults to false, meaning a file is required to be uploaded.
  66. * When no file is uploaded, the owner attribute is set to null to prevent
  67. * setting arbitrary values.
  68. */
  69. public $allowEmpty=false;
  70. /**
  71. * @var mixed a list of file name extensions that are allowed to be uploaded.
  72. * This can be either an array or a string consisting of file extension names
  73. * separated by space or comma (e.g. "gif, jpg").
  74. * Extension names are case-insensitive. Defaults to null, meaning all file name
  75. * extensions are allowed.
  76. */
  77. public $types;
  78. /**
  79. * @var mixed a list of MIME-types of the file that are allowed to be uploaded.
  80. * This can be either an array or a string consisting of MIME-types separated
  81. * by space or comma (e.g. "image/gif, image/jpeg"). MIME-types are
  82. * case-insensitive. Defaults to null, meaning all MIME-types are allowed.
  83. * In order to use this property fileinfo PECL extension should be installed.
  84. * @since 1.1.11
  85. */
  86. public $mimeTypes;
  87. /**
  88. * @var integer the minimum number of bytes required for the uploaded file.
  89. * Defaults to null, meaning no limit.
  90. * @see tooSmall
  91. */
  92. public $minSize;
  93. /**
  94. * @var integer the maximum number of bytes required for the uploaded file.
  95. * Defaults to null, meaning no limit.
  96. * Note, the size limit is also affected by 'upload_max_filesize' INI setting
  97. * and the 'MAX_FILE_SIZE' hidden field value.
  98. * @see tooLarge
  99. */
  100. public $maxSize;
  101. /**
  102. * @var string the error message used when the uploaded file is too large.
  103. * @see maxSize
  104. */
  105. public $tooLarge;
  106. /**
  107. * @var string the error message used when the uploaded file is too small.
  108. * @see minSize
  109. */
  110. public $tooSmall;
  111. /**
  112. * @var string the error message used when the uploaded file has an extension name
  113. * that is not listed among {@link types}.
  114. */
  115. public $wrongType;
  116. /**
  117. * @var string the error message used when the uploaded file has a MIME-type
  118. * that is not listed among {@link mimeTypes}. In order to use this property
  119. * fileinfo PECL extension should be installed.
  120. * @since 1.1.11
  121. */
  122. public $wrongMimeType;
  123. /**
  124. * @var integer the maximum file count the given attribute can hold.
  125. * It defaults to 1, meaning single file upload. By defining a higher number,
  126. * multiple uploads become possible.
  127. */
  128. public $maxFiles=1;
  129. /**
  130. * @var string the error message used if the count of multiple uploads exceeds
  131. * limit.
  132. */
  133. public $tooMany;
  134. /**
  135. * Set the attribute and then validates using {@link validateFile}.
  136. * If there is any error, the error message is added to the object.
  137. * @param CModel $object the object being validated
  138. * @param string $attribute the attribute being validated
  139. */
  140. protected function validateAttribute($object, $attribute)
  141. {
  142. $files=$object->$attribute;
  143. if($this->maxFiles > 1)
  144. {
  145. if(!is_array($files) || !isset($files[0]) || !$files[0] instanceof CUploadedFile)
  146. $files = CUploadedFile::getInstances($object, $attribute);
  147. if(array()===$files)
  148. return $this->emptyAttribute($object, $attribute);
  149. if(count($files) > $this->maxFiles)
  150. {
  151. $message=$this->tooMany!==null?$this->tooMany : Yii::t('yii', '{attribute} cannot accept more than {limit} files.');
  152. $this->addError($object, $attribute, $message, array('{attribute}'=>$attribute, '{limit}'=>$this->maxFiles));
  153. }
  154. else
  155. foreach($files as $file)
  156. $this->validateFile($object, $attribute, $file);
  157. }
  158. else
  159. {
  160. if (is_array($files))
  161. {
  162. if (count($files) > 1)
  163. {
  164. $message=$this->tooMany!==null?$this->tooMany : Yii::t('yii', '{attribute} cannot accept more than {limit} files.');
  165. $this->addError($object, $attribute, $message, array('{attribute}'=>$attribute, '{limit}'=>$this->maxFiles));
  166. return;
  167. }
  168. else
  169. $file = empty($files) ? null : reset($files);
  170. }
  171. else
  172. $file = $files;
  173. if(!$file instanceof CUploadedFile)
  174. {
  175. $file = CUploadedFile::getInstance($object, $attribute);
  176. if(null===$file)
  177. return $this->emptyAttribute($object, $attribute);
  178. }
  179. $this->validateFile($object, $attribute, $file);
  180. }
  181. }
  182. /**
  183. * Internally validates a file object.
  184. * @param CModel $object the object being validated
  185. * @param string $attribute the attribute being validated
  186. * @param CUploadedFile $file uploaded file passed to check against a set of rules
  187. * @throws CException if failed to upload the file
  188. */
  189. protected function validateFile($object, $attribute, $file)
  190. {
  191. $error=(null===$file ? null : $file->getError());
  192. if($error==UPLOAD_ERR_INI_SIZE || $error==UPLOAD_ERR_FORM_SIZE || $this->maxSize!==null && $file->getSize()>$this->maxSize)
  193. {
  194. $message=$this->tooLarge!==null?$this->tooLarge : Yii::t('yii','The file "{file}" is too large. Its size cannot exceed {limit} bytes.');
  195. $this->addError($object,$attribute,$message,array('{file}'=>CHtml::encode($file->getName()), '{limit}'=>$this->getSizeLimit()));
  196. if($error!==UPLOAD_ERR_OK)
  197. return;
  198. }
  199. elseif($error!==UPLOAD_ERR_OK)
  200. {
  201. if($error==UPLOAD_ERR_NO_FILE)
  202. return $this->emptyAttribute($object, $attribute);
  203. elseif($error==UPLOAD_ERR_PARTIAL)
  204. throw new CException(Yii::t('yii','The file "{file}" was only partially uploaded.',array('{file}'=>CHtml::encode($file->getName()))));
  205. elseif($error==UPLOAD_ERR_NO_TMP_DIR)
  206. throw new CException(Yii::t('yii','Missing the temporary folder to store the uploaded file "{file}".',array('{file}'=>CHtml::encode($file->getName()))));
  207. elseif($error==UPLOAD_ERR_CANT_WRITE)
  208. throw new CException(Yii::t('yii','Failed to write the uploaded file "{file}" to disk.',array('{file}'=>CHtml::encode($file->getName()))));
  209. elseif(defined('UPLOAD_ERR_EXTENSION') && $error==UPLOAD_ERR_EXTENSION) // available for PHP 5.2.0 or above
  210. throw new CException(Yii::t('yii','A PHP extension stopped the file upload.'));
  211. else
  212. throw new CException(Yii::t('yii','Unable to upload the file "{file}" because of an unrecognized error.',array('{file}'=>CHtml::encode($file->getName()))));
  213. }
  214. if($this->minSize!==null && $file->getSize()<$this->minSize)
  215. {
  216. $message=$this->tooSmall!==null?$this->tooSmall : Yii::t('yii','The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.');
  217. $this->addError($object,$attribute,$message,array('{file}'=>CHtml::encode($file->getName()), '{limit}'=>$this->minSize));
  218. }
  219. if($this->types!==null)
  220. {
  221. if(is_string($this->types))
  222. $types=preg_split('/[\s,]+/',strtolower($this->types),-1,PREG_SPLIT_NO_EMPTY);
  223. else
  224. $types=$this->types;
  225. if(!in_array(strtolower($file->getExtensionName()),$types))
  226. {
  227. $message=$this->wrongType!==null?$this->wrongType : Yii::t('yii','The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.');
  228. $this->addError($object,$attribute,$message,array('{file}'=>CHtml::encode($file->getName()), '{extensions}'=>implode(', ',$types)));
  229. }
  230. }
  231. if($this->mimeTypes!==null && !empty($file->tempName))
  232. {
  233. if(function_exists('finfo_open'))
  234. {
  235. $mimeType=false;
  236. if($info=finfo_open(defined('FILEINFO_MIME_TYPE') ? FILEINFO_MIME_TYPE : FILEINFO_MIME))
  237. $mimeType=finfo_file($info,$file->getTempName());
  238. }
  239. elseif(function_exists('mime_content_type'))
  240. $mimeType=mime_content_type($file->getTempName());
  241. else
  242. throw new CException(Yii::t('yii','In order to use MIME-type validation provided by CFileValidator fileinfo PECL extension should be installed.'));
  243. if(is_string($this->mimeTypes))
  244. $mimeTypes=preg_split('/[\s,]+/',strtolower($this->mimeTypes),-1,PREG_SPLIT_NO_EMPTY);
  245. else
  246. $mimeTypes=$this->mimeTypes;
  247. if($mimeType===false || !in_array(strtolower($mimeType),$mimeTypes))
  248. {
  249. $message=$this->wrongMimeType!==null?$this->wrongMimeType : Yii::t('yii','The file "{file}" cannot be uploaded. Only files of these MIME-types are allowed: {mimeTypes}.');
  250. $this->addError($object,$attribute,$message,array('{file}'=>CHtml::encode($file->getName()), '{mimeTypes}'=>implode(', ',$mimeTypes)));
  251. }
  252. }
  253. }
  254. /**
  255. * Raises an error to inform end user about blank attribute.
  256. * Sets the owner attribute to null to prevent setting arbitrary values.
  257. * @param CModel $object the object being validated
  258. * @param string $attribute the attribute being validated
  259. */
  260. protected function emptyAttribute($object, $attribute)
  261. {
  262. if($this->safe)
  263. $object->$attribute=null;
  264. if(!$this->allowEmpty)
  265. {
  266. $message=$this->message!==null?$this->message : Yii::t('yii','{attribute} cannot be blank.');
  267. $this->addError($object,$attribute,$message);
  268. }
  269. }
  270. /**
  271. * Returns the maximum size allowed for uploaded files.
  272. * This is determined based on three factors:
  273. * <ul>
  274. * <li>'upload_max_filesize' in php.ini</li>
  275. * <li>'MAX_FILE_SIZE' hidden field</li>
  276. * <li>{@link maxSize}</li>
  277. * </ul>
  278. *
  279. * @return integer the size limit for uploaded files.
  280. */
  281. protected function getSizeLimit()
  282. {
  283. $limit=ini_get('upload_max_filesize');
  284. $limit=$this->sizeToBytes($limit);
  285. if($this->maxSize!==null && $limit>0 && $this->maxSize<$limit)
  286. $limit=$this->maxSize;
  287. if(isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE']>0 && $_POST['MAX_FILE_SIZE']<$limit)
  288. $limit=$_POST['MAX_FILE_SIZE'];
  289. return $limit;
  290. }
  291. /**
  292. * Converts php.ini style size to bytes. Examples of size strings are: 150, 1g, 500k, 5M (size suffix
  293. * is case insensitive). If you pass here the number with a fractional part, then everything after
  294. * the decimal point will be ignored (php.ini values common behavior). For example 1.5G value would be
  295. * treated as 1G and 1073741824 number will be returned as a result. This method is public
  296. * (was private before) since 1.1.11.
  297. *
  298. * @param string $sizeStr the size string to convert.
  299. * @return integer the byte count in the given size string.
  300. * @since 1.1.11
  301. */
  302. public function sizeToBytes($sizeStr)
  303. {
  304. // get the latest character
  305. switch (strtolower(substr($sizeStr, -1)))
  306. {
  307. case 'm': return (int)$sizeStr * 1048576; // 1024 * 1024
  308. case 'k': return (int)$sizeStr * 1024; // 1024
  309. case 'g': return (int)$sizeStr * 1073741824; // 1024 * 1024 * 1024
  310. default: return (int)$sizeStr; // do nothing
  311. }
  312. }
  313. }