UploadFile.class.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2009 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. // $Id$
  12. /**
  13. +------------------------------------------------------------------------------
  14. * 文件上传类
  15. +------------------------------------------------------------------------------
  16. * @category ORG
  17. * @package ORG
  18. * @subpackage Net
  19. * @author liu21st <liu21st@gmail.com>
  20. * @version $Id$
  21. +------------------------------------------------------------------------------
  22. */
  23. class UploadFile extends Think
  24. {//类定义开始
  25. // 上传文件的最大值
  26. public $maxSize = -1;
  27. // 是否支持多文件上传
  28. public $supportMulti = true;
  29. // 允许上传的文件后缀
  30. // 留空不作后缀检查
  31. public $allowExts = array();
  32. // 允许上传的文件类型
  33. // 留空不做检查
  34. public $allowTypes = array();
  35. // 使用对上传图片进行缩略图处理
  36. public $thumb = false;
  37. // 缩略图最大宽度
  38. public $thumbMaxWidth;
  39. // 缩略图最大高度
  40. public $thumbMaxHeight;
  41. // 缩略图前缀
  42. public $thumbPrefix = 'thumb_';
  43. public $thumbSuffix = '';
  44. // 缩略图保存路径
  45. public $thumbPath = '';
  46. // 缩略图文件名
  47. public $thumbFile = '';
  48. // 是否移除原图
  49. public $thumbRemoveOrigin = false;
  50. // 压缩图片文件上传
  51. public $zipImages = false;
  52. // 启用子目录保存文件
  53. public $autoSub = false;
  54. // 子目录创建方式 可以使用hash date
  55. public $subType = 'hash';
  56. public $dateFormat = 'Ymd';
  57. public $hashLevel = 1; // hash的目录层次
  58. // 上传文件保存路径
  59. public $savePath = '';
  60. public $autoCheck = true; // 是否自动检查附件
  61. // 存在同名是否覆盖
  62. public $uploadReplace = false;
  63. // 上传文件命名规则
  64. // 例如可以是 time uniqid com_create_guid 等
  65. // 必须是一个无需任何参数的函数名 可以使用自定义函数
  66. public $saveRule = '';
  67. // 上传文件Hash规则函数名
  68. // 例如可以是 md5_file sha1_file 等
  69. public $hashType = 'md5_file';
  70. // 错误信息
  71. private $error = '';
  72. // 上传成功的文件信息
  73. private $uploadFileInfo ;
  74. // 禁止上传的文件后缀
  75. // 留空不作后缀检查
  76. public $denyExts = array("exe","php","asp","aspx","bat","msi","vbs","js","cmd","scr","com","jar");
  77. // 禁止上传的文件类型
  78. // 留空不做检查
  79. public $denyTypes = array();
  80. /**
  81. +----------------------------------------------------------
  82. * 架构函数
  83. +----------------------------------------------------------
  84. * @access public
  85. +----------------------------------------------------------
  86. */
  87. public function __construct($maxSize='',$allowExts='',$allowTypes='',$savePath='',$saveRule='')
  88. {
  89. if(!empty($maxSize) && is_numeric($maxSize)) {
  90. $this->maxSize = $maxSize;
  91. }
  92. if(!empty($allowExts)) {
  93. if(is_array($allowExts)) {
  94. $this->allowExts = array_map('strtolower',$allowExts);
  95. }else {
  96. $this->allowExts = explode(',',strtolower($allowExts));
  97. }
  98. }
  99. if(!empty($allowTypes)) {
  100. if(is_array($allowTypes)) {
  101. $this->allowTypes = array_map('strtolower',$allowTypes);
  102. }else {
  103. $this->allowTypes = explode(',',strtolower($allowTypes));
  104. }
  105. }
  106. if(!empty($saveRule)) {
  107. $this->saveRule = $saveRule;
  108. }else{
  109. $this->saveRule = C('UPLOAD_FILE_RULE');
  110. }
  111. $this->savePath = $savePath;
  112. }
  113. /**
  114. +----------------------------------------------------------
  115. * 上传一个文件
  116. +----------------------------------------------------------
  117. * @access public
  118. +----------------------------------------------------------
  119. * @param mixed $name 数据
  120. * @param string $value 数据表名
  121. +----------------------------------------------------------
  122. * @return string
  123. +----------------------------------------------------------
  124. * @throws ThinkExecption
  125. +----------------------------------------------------------
  126. */
  127. private function save($file)
  128. {
  129. $filename = $file['savepath'].$file['savename'];
  130. if(!$this->uploadReplace && is_file($filename)) {
  131. // 不覆盖同名文件
  132. $this->error = l("FILE_EXIST").$filename;
  133. return false;
  134. }
  135. // 如果是图像文件 检测文件格式
  136. if( in_array(strtolower($file['extension']),array('gif','jpg','jpeg','bmp','png','swf')) && false === getimagesize($file['tmp_name'])) {
  137. $this->error = l("INVALID_IMAGE");
  138. return false;
  139. }
  140. if(!move_uploaded_file($file['tmp_name'], auto_charset($filename,'utf-8','gbk'))) {
  141. $this->error = L("SAVE_IMAGE_ERROR");
  142. return false;
  143. }
  144. if($this->thumb && in_array(strtolower($file['extension']),array('gif','jpg','jpeg','bmp','png'))) {
  145. $image = getimagesize($filename);
  146. if(false !== $image) {
  147. //是图像文件生成缩略图
  148. $thumbWidth = explode(',',$this->thumbMaxWidth);
  149. $thumbHeight = explode(',',$this->thumbMaxHeight);
  150. $thumbPrefix = explode(',',$this->thumbPrefix);
  151. $thumbSuffix = explode(',',$this->thumbSuffix);
  152. $thumbFile = explode(',',$this->thumbFile);
  153. $thumbPath = $this->thumbPath?$this->thumbPath:$file['savepath'];
  154. // 生成图像缩略图
  155. import("ORG.Util.Image");
  156. for($i=0,$len=count($thumbWidth); $i<$len; $i++) {
  157. $thumbname = $thumbPath.$thumbPrefix[$i].substr($file['savename'],0,strrpos($file['savename'], '.')).$thumbSuffix[$i].'.'.$file['extension'];
  158. Image::thumb($filename,$thumbname,'',$thumbWidth[$i],$thumbHeight[$i],true);
  159. }
  160. if($this->thumbRemoveOrigin) {
  161. // 生成缩略图之后删除原图
  162. unlink($filename);
  163. }
  164. }
  165. }
  166. if($this->zipImags) {
  167. // TODO 对图片压缩包在线解压
  168. }
  169. return true;
  170. }
  171. /**
  172. +----------------------------------------------------------
  173. * 上传文件
  174. +----------------------------------------------------------
  175. * @access public
  176. +----------------------------------------------------------
  177. * @param string $savePath 上传文件保存路径
  178. +----------------------------------------------------------
  179. * @return string
  180. +----------------------------------------------------------
  181. * @throws ThinkExecption
  182. +----------------------------------------------------------
  183. */
  184. public function upload($savePath ='')
  185. {
  186. //如果不指定保存文件名,则由系统默认
  187. if(empty($savePath))
  188. $savePath = $this->savePath;
  189. // 检查上传目录
  190. if(!is_dir($savePath)) {
  191. // 检查目录是否编码后的
  192. if(is_dir(base64_decode($savePath))) {
  193. $savePath = base64_decode($savePath);
  194. }else{
  195. // 尝试创建目录
  196. if(!mkdir($savePath)){
  197. $this->error = sprintf(L("UPLOAD_DIR_NOT_EXIST"),$savePath);
  198. return false;
  199. }
  200. }
  201. }else {
  202. if(!is_writeable($savePath)) {
  203. $this->error = sprintf(L("UPLOAD_DIR_NOT_WRITE"),$savePath);
  204. return false;
  205. }
  206. }
  207. $fileInfo = array();
  208. $isUpload = false;
  209. // 获取上传的文件信息
  210. // 对$_FILES数组信息处理
  211. $files = $this->dealFiles($_FILES);
  212. ;
  213. foreach($files as $key => $file) {
  214. //过滤无效的上传
  215. if(!empty($file['name'])) {
  216. //登记上传文件的扩展信息
  217. $file['key'] = $key;
  218. $file['extension'] = $this->getExt($file['name']);
  219. $file['savepath'] = $savePath;
  220. $file['savename'] = $this->getSaveName($file);
  221. // 自动检查附件
  222. if($this->autoCheck) {
  223. if(!$this->check($file))
  224. return false;
  225. }
  226. //保存上传文件
  227. if(!$this->save($file)) return false;
  228. if(function_exists($this->hashType)) {
  229. $fun = $this->hashType;
  230. $file['hash'] = $fun(auto_charset($file['savepath'].$file['savename'],'utf-8','gbk'));
  231. }
  232. //上传成功后保存文件信息,供其他地方调用
  233. unset($file['tmp_name'],$file['error']);
  234. $fileInfo[] = $file;
  235. $isUpload = true;
  236. }
  237. }
  238. if($isUpload) {
  239. $this->uploadFileInfo = $fileInfo;
  240. return true;
  241. }else {
  242. $this->error = l("NOT_FILE_SELECTED");
  243. return false;
  244. }
  245. }
  246. /**
  247. +----------------------------------------------------------
  248. * 转换上传文件数组变量为正确的方式
  249. +----------------------------------------------------------
  250. * @access private
  251. +----------------------------------------------------------
  252. * @param array $files 上传的文件变量
  253. +----------------------------------------------------------
  254. * @return array
  255. +----------------------------------------------------------
  256. */
  257. private function dealFiles($files) {
  258. $fileArray = array();
  259. foreach ($files as $file){
  260. if(is_array($file['name'])) {
  261. $keys = array_keys($file);
  262. $count = count($file['name']);
  263. for ($i=0; $i<$count; $i++) {
  264. foreach ($keys as $key)
  265. $fileArray[$i][$key] = $file[$key][$i];
  266. }
  267. }else{
  268. $fileArray = $files;
  269. }
  270. break;
  271. }
  272. return $fileArray;
  273. }
  274. /**
  275. +----------------------------------------------------------
  276. * 获取错误代码信息
  277. +----------------------------------------------------------
  278. * @access public
  279. +----------------------------------------------------------
  280. * @param string $errorNo 错误号码
  281. +----------------------------------------------------------
  282. * @return void
  283. +----------------------------------------------------------
  284. * @throws ThinkExecption
  285. +----------------------------------------------------------
  286. */
  287. protected function error($errorNo)
  288. {
  289. switch($errorNo) {
  290. case 1:
  291. $this->error = l("FILE_ERROR_1");
  292. break;
  293. case 2:
  294. $this->error = l("FILE_ERROR_2");
  295. break;
  296. case 3:
  297. $this->error = l("FILE_ERROR_3");
  298. break;
  299. case 4:
  300. $this->error = l("FILE_ERROR_4");
  301. break;
  302. case 6:
  303. $this->error = l("FILE_ERROR_6");
  304. break;
  305. case 7:
  306. $this->error = l("FILE_ERROR_7");
  307. break;
  308. default:
  309. $this->error = l("FILE_ERROR_UNKNOWN");
  310. }
  311. return ;
  312. }
  313. /**
  314. +----------------------------------------------------------
  315. * 根据上传文件命名规则取得保存文件名
  316. +----------------------------------------------------------
  317. * @access private
  318. +----------------------------------------------------------
  319. * @param string $filename 数据
  320. +----------------------------------------------------------
  321. * @return string
  322. +----------------------------------------------------------
  323. */
  324. private function getSaveName($filename)
  325. {
  326. $rule = $this->saveRule;
  327. if(empty($rule)) {//没有定义命名规则,则保持文件名不变
  328. $saveName = $filename['name'];
  329. }else {
  330. if(function_exists($rule)) {
  331. //使用函数生成一个唯一文件标识号
  332. $saveName = $rule().".".$filename['extension'];
  333. }else {
  334. //使用给定的文件名作为标识号
  335. $saveName = $rule.".".$filename['extension'];
  336. }
  337. }
  338. if($this->autoSub) {
  339. // 使用子目录保存文件
  340. $saveName = $this->getSubName($filename).'/'.$saveName;
  341. }
  342. return $saveName;
  343. }
  344. /**
  345. +----------------------------------------------------------
  346. * 获取子目录的名称
  347. +----------------------------------------------------------
  348. * @access private
  349. +----------------------------------------------------------
  350. * @param array $file 上传的文件信息
  351. +----------------------------------------------------------
  352. * @return string
  353. +----------------------------------------------------------
  354. */
  355. private function getSubName($file)
  356. {
  357. switch($this->subType) {
  358. case 'date':
  359. $dir = toDate(gmtTime(),$this->dateFormat);
  360. break;
  361. case 'hash':
  362. default:
  363. $name = md5($file['savename']);
  364. $dir = '';
  365. for($i=0;$i<$this->hashLevel;$i++) {
  366. $dir .= $name{0}.'/';
  367. }
  368. break;
  369. }
  370. if(!is_dir($file['savepath'].$dir)) {
  371. mk_dir($file['savepath'].$dir);
  372. }
  373. return $dir;
  374. }
  375. /**
  376. +----------------------------------------------------------
  377. * 检查上传的文件
  378. +----------------------------------------------------------
  379. * @access private
  380. +----------------------------------------------------------
  381. * @param array $file 文件信息
  382. +----------------------------------------------------------
  383. * @return boolean
  384. +----------------------------------------------------------
  385. */
  386. private function check($file) {
  387. if($file['error']!== 0) {
  388. //文件上传失败
  389. //捕获错误代码
  390. $this->error($file['error']);
  391. return false;
  392. }
  393. //文件上传成功,进行自定义规则检查
  394. //检查文件类型
  395. if(!$this->checkExt($file['extension'])) {
  396. $this->error = l("FILE_TYPE_NOT_ALLOW");
  397. return false;
  398. }
  399. //检查文件大小
  400. if(!$this->checkSize($file['size'])) {
  401. $this->error = l("SIZE_NOT_MATCH");
  402. return false;
  403. }
  404. //检查文件Mime类型
  405. if(!$this->checkType($file['type'])) {
  406. $this->error = l("MIME_NOT_ALLOW");
  407. return false;
  408. }
  409. //检查是否合法上传
  410. if(!$this->checkUpload($file['tmp_name'])) {
  411. $this->error = l("INVALID_UPLOAD");
  412. return false;
  413. }
  414. return true;
  415. }
  416. /**
  417. +----------------------------------------------------------
  418. * 检查上传的文件类型是否合法
  419. +----------------------------------------------------------
  420. * @access private
  421. +----------------------------------------------------------
  422. * @param string $type 数据
  423. +----------------------------------------------------------
  424. * @return boolean
  425. +----------------------------------------------------------
  426. */
  427. private function checkType($type)
  428. {
  429. $bln = true;
  430. if(!empty($this->allowTypes))
  431. $bln = in_array(strtolower($type),$this->allowTypes);
  432. if($bln)
  433. {
  434. if(!empty($this->denyTypes))
  435. $bln = !in_array(strtolower($ext),$this->denyTypes,true);
  436. }
  437. return $bln;
  438. }
  439. /**
  440. +----------------------------------------------------------
  441. * 检查上传的文件后缀是否合法
  442. +----------------------------------------------------------
  443. * @access private
  444. +----------------------------------------------------------
  445. * @param string $ext 后缀名
  446. +----------------------------------------------------------
  447. * @return boolean
  448. +----------------------------------------------------------
  449. */
  450. private function checkExt($ext)
  451. {
  452. $bln = true;
  453. if(!empty($this->allowExts))
  454. {
  455. $bln = in_array(strtolower($ext),$this->allowExts,true);
  456. }
  457. if($bln)
  458. {
  459. if(!empty($this->denyExts))
  460. $bln = !in_array(strtolower($ext),$this->denyExts,true);
  461. }
  462. return $bln;
  463. }
  464. /**
  465. +----------------------------------------------------------
  466. * 检查文件大小是否合法
  467. +----------------------------------------------------------
  468. * @access private
  469. +----------------------------------------------------------
  470. * @param integer $size 数据
  471. +----------------------------------------------------------
  472. * @return boolean
  473. +----------------------------------------------------------
  474. */
  475. private function checkSize($size)
  476. {
  477. return !($size > $this->maxSize) || (-1 == $this->maxSize);
  478. }
  479. /**
  480. +----------------------------------------------------------
  481. * 检查文件是否非法提交
  482. +----------------------------------------------------------
  483. * @access private
  484. +----------------------------------------------------------
  485. * @param string $filename 文件名
  486. +----------------------------------------------------------
  487. * @return boolean
  488. +----------------------------------------------------------
  489. */
  490. private function checkUpload($filename)
  491. {
  492. return is_uploaded_file($filename);
  493. }
  494. /**
  495. +----------------------------------------------------------
  496. * 取得上传文件的后缀
  497. +----------------------------------------------------------
  498. * @access private
  499. +----------------------------------------------------------
  500. * @param string $filename 文件名
  501. +----------------------------------------------------------
  502. * @return boolean
  503. +----------------------------------------------------------
  504. */
  505. private function getExt($filename)
  506. {
  507. $pathinfo = pathinfo($filename);
  508. return $pathinfo['extension'];
  509. }
  510. /**
  511. +----------------------------------------------------------
  512. * 取得上传文件的信息
  513. +----------------------------------------------------------
  514. * @access public
  515. +----------------------------------------------------------
  516. * @return array
  517. +----------------------------------------------------------
  518. */
  519. public function getUploadFileInfo()
  520. {
  521. return $this->uploadFileInfo;
  522. }
  523. /**
  524. +----------------------------------------------------------
  525. * 取得最后一次错误信息
  526. +----------------------------------------------------------
  527. * @access public
  528. +----------------------------------------------------------
  529. * @return string
  530. +----------------------------------------------------------
  531. */
  532. public function getErrorMsg()
  533. {
  534. return $this->error;
  535. }
  536. }//类定义结束
  537. ?>