Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

433 linhas
14 KiB

há 3 anos
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: 麦当苗儿 <zuojiazi@vip.qq.com> <http://www.zjzit.cn>
  10. // +----------------------------------------------------------------------
  11. namespace Think;
  12. class Upload {
  13. /**
  14. * 默认上传配置
  15. * @var array
  16. */
  17. private $config = array(
  18. 'mimes' => array(), //允许上传的文件MiMe类型
  19. 'maxSize' => 0, //上传的文件大小限制 (0-不做限制)
  20. 'exts' => array(), //允许上传的文件后缀
  21. 'autoSub' => true, //自动子目录保存文件
  22. 'subName' => array('date', 'Y-m-d'), //子目录创建方式,[0]-函数名,[1]-参数,多个参数使用数组
  23. 'rootPath' => './Uploads/', //保存根路径
  24. 'savePath' => '', //保存路径
  25. 'saveName' => array('uniqid', ''), //上传文件命名规则,[0]-函数名,[1]-参数,多个参数使用数组
  26. 'saveExt' => '', //文件保存后缀,空则使用原后缀
  27. 'replace' => false, //存在同名是否覆盖
  28. 'hash' => true, //是否生成hash编码
  29. 'callback' => false, //检测文件是否存在回调,如果存在返回文件信息数组
  30. 'driver' => '', // 文件上传驱动
  31. 'driverConfig' => array(), // 上传驱动配置
  32. );
  33. /**
  34. * 上传错误信息
  35. * @var string
  36. */
  37. private $error = ''; //上传错误信息
  38. /**
  39. * 上传驱动实例
  40. * @var Object
  41. */
  42. private $uploader;
  43. /**
  44. * 构造方法,用于构造上传实例
  45. * @param array $config 配置
  46. * @param string $driver 要使用的上传驱动 LOCAL-本地上传驱动,FTP-FTP上传驱动
  47. */
  48. public function __construct($config = array(), $driver = '', $driverConfig = null){
  49. /* 获取配置 */
  50. $this->config = array_merge($this->config, $config);
  51. /* 设置上传驱动 */
  52. $this->setDriver($driver, $driverConfig);
  53. /* 调整配置,把字符串配置参数转换为数组 */
  54. if(!empty($this->config['mimes'])){
  55. if(is_string($this->mimes)) {
  56. $this->config['mimes'] = explode(',', $this->mimes);
  57. }
  58. $this->config['mimes'] = array_map('strtolower', $this->mimes);
  59. }
  60. if(!empty($this->config['exts'])){
  61. if (is_string($this->exts)){
  62. $this->config['exts'] = explode(',', $this->exts);
  63. }
  64. $this->config['exts'] = array_map('strtolower', $this->exts);
  65. }
  66. }
  67. /**
  68. * 使用 $this->name 获取配置
  69. * @param string $name 配置名称
  70. * @return multitype 配置值
  71. */
  72. public function __get($name) {
  73. return $this->config[$name];
  74. }
  75. public function __set($name,$value){
  76. if(isset($this->config[$name])) {
  77. $this->config[$name] = $value;
  78. if($name == 'driverConfig'){
  79. //改变驱动配置后重置上传驱动
  80. //注意:必须选改变驱动然后再改变驱动配置
  81. $this->setDriver();
  82. }
  83. }
  84. }
  85. public function __isset($name){
  86. return isset($this->config[$name]);
  87. }
  88. /**
  89. * 获取最后一次上传错误信息
  90. * @return string 错误信息
  91. */
  92. public function getError(){
  93. return $this->error;
  94. }
  95. /**
  96. * 上传单个文件
  97. * @param array $file 文件数组
  98. * @return array 上传成功后的文件信息
  99. */
  100. public function uploadOne($file){
  101. $info = $this->upload(array($file));
  102. return $info ? $info[0] : $info;
  103. }
  104. /**
  105. * 上传文件
  106. * @param 文件信息数组 $files ,通常是 $_FILES数组
  107. */
  108. public function upload($files='') {
  109. if('' === $files){
  110. $files = $_FILES;
  111. }
  112. if(empty($files)){
  113. $this->error = '没有上传的文件!';
  114. return false;
  115. }
  116. /* 检测上传根目录 */
  117. if(!$this->uploader->checkRootPath($this->rootPath)){
  118. $this->error = $this->uploader->getError();
  119. return false;
  120. }
  121. /* 检查上传目录 */
  122. if(!$this->uploader->checkSavePath($this->savePath)){
  123. $this->error = $this->uploader->getError();
  124. return false;
  125. }
  126. /* 逐个检测并上传文件 */
  127. $info = array();
  128. if(function_exists('finfo_open')){
  129. $finfo = finfo_open ( FILEINFO_MIME_TYPE );
  130. }
  131. // 对上传文件数组信息处理
  132. $files = $this->dealFiles($files);
  133. foreach ($files as $key => $file) {
  134. $file['name'] = strip_tags($file['name']);
  135. if(!isset($file['key'])) $file['key'] = $key;
  136. /* 通过扩展获取文件类型,可解决FLASH上传$FILES数组返回文件类型错误的问题 */
  137. if(isset($finfo)){
  138. $file['type'] = finfo_file ( $finfo , $file['tmp_name'] );
  139. }
  140. /* 获取上传文件后缀,允许上传无后缀文件 */
  141. $file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION);
  142. /* 文件上传检测 */
  143. if (!$this->check($file)){
  144. continue;
  145. }
  146. /* 获取文件hash */
  147. if($this->hash){
  148. $file['md5'] = md5_file($file['tmp_name']);
  149. $file['sha1'] = sha1_file($file['tmp_name']);
  150. }
  151. /* 调用回调函数检测文件是否存在 */
  152. if($this->callback){
  153. $data = call_user_func($this->callback, $file);
  154. }else{
  155. $data = false;
  156. }
  157. if( $this->callback && $data ){
  158. if ( file_exists('.'.$data['path']) ) {
  159. $info[$key] = $data;
  160. continue;
  161. }elseif($this->removeTrash){
  162. call_user_func($this->removeTrash,$data);//删除垃圾据
  163. }
  164. }
  165. /* 生成保存文件名 */
  166. $savename = $this->getSaveName($file);
  167. if(false == $savename){
  168. continue;
  169. } else {
  170. $file['savename'] = $savename;
  171. }
  172. /* 检测并创建子目录 */
  173. $subpath = $this->getSubPath($file['name']);
  174. if(false === $subpath){
  175. continue;
  176. } else {
  177. $file['savepath'] = $this->savePath . $subpath;
  178. }
  179. /* 对图像文件进行严格检测 */
  180. $ext = strtolower($file['ext']);
  181. if(in_array($ext, array('gif','jpg','jpeg','bmp','png','swf'))) {
  182. $imginfo = getimagesize($file['tmp_name']);
  183. if(empty($imginfo) || ($ext == 'gif' && empty($imginfo['bits']))){
  184. $this->error = '非法图像文件!';
  185. continue;
  186. }
  187. }
  188. /* 保存文件 并记录保存成功的文件 */
  189. if ($this->uploader->save($file,$this->replace)) {
  190. unset($file['error'], $file['tmp_name']);
  191. $info[$key] = $file;
  192. } else {
  193. $this->error = $this->uploader->getError();
  194. }
  195. }
  196. if(isset($finfo)){
  197. finfo_close($finfo);
  198. }
  199. return empty($info) ? false : $info;
  200. }
  201. /**
  202. * 转换上传文件数组变量为正确的方式
  203. * @access private
  204. * @param array $files 上传的文件变量
  205. * @return array
  206. */
  207. private function dealFiles($files) {
  208. $fileArray = array();
  209. $n = 0;
  210. foreach ($files as $key=>$file){
  211. if(is_array($file['name'])) {
  212. $keys = array_keys($file);
  213. $count = count($file['name']);
  214. for ($i=0; $i<$count; $i++) {
  215. $fileArray[$n]['key'] = $key;
  216. foreach ($keys as $_key){
  217. $fileArray[$n][$_key] = $file[$_key][$i];
  218. }
  219. $n++;
  220. }
  221. }else{
  222. $fileArray = $files;
  223. break;
  224. }
  225. }
  226. return $fileArray;
  227. }
  228. /**
  229. * 设置上传驱动
  230. * @param string $driver 驱动名称
  231. * @param array $config 驱动配置
  232. */
  233. private function setDriver($driver = null, $config = null){
  234. $driver = $driver ? : ($this->driver ? : C('FILE_UPLOAD_TYPE'));
  235. $config = $config ? : ($this->driverConfig ? : C('UPLOAD_TYPE_CONFIG'));
  236. $class = strpos($driver,'\\')? $driver : 'Think\\Upload\\Driver\\'.ucfirst(strtolower($driver));
  237. $this->uploader = new $class($config);
  238. if(!$this->uploader){
  239. E("不存在上传驱动:{$name}");
  240. }
  241. }
  242. /**
  243. * 检查上传的文件
  244. * @param array $file 文件信息
  245. */
  246. private function check($file) {
  247. /* 文件上传失败,捕获错误代码 */
  248. if ($file['error']) {
  249. $this->error($file['error']);
  250. return false;
  251. }
  252. /* 无效上传 */
  253. if (empty($file['name'])){
  254. $this->error = '未知上传错误!';
  255. }
  256. /* 检查是否合法上传 */
  257. if (!is_uploaded_file($file['tmp_name'])) {
  258. $this->error = '非法上传文件!';
  259. return false;
  260. }
  261. /* 检查文件大小 */
  262. if (!$this->checkSize($file['size'])) {
  263. $this->error = '上传文件大小不符!';
  264. return false;
  265. }
  266. /* 检查文件Mime类型 */
  267. //TODO:FLASH上传的文件获取到的mime类型都为application/octet-stream
  268. if (!$this->checkMime($file['type'])) {
  269. $this->error = '上传文件MIME类型不允许!';
  270. return false;
  271. }
  272. /* 检查文件后缀 */
  273. if (!$this->checkExt($file['ext'])) {
  274. $this->error = '上传文件后缀不允许';
  275. return false;
  276. }
  277. /* 通过检测 */
  278. return true;
  279. }
  280. /**
  281. * 获取错误代码信息
  282. * @param string $errorNo 错误号
  283. */
  284. private function error($errorNo) {
  285. switch ($errorNo) {
  286. case 1:
  287. $this->error = '上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值!';
  288. break;
  289. case 2:
  290. $this->error = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值!';
  291. break;
  292. case 3:
  293. $this->error = '文件只有部分被上传!';
  294. break;
  295. case 4:
  296. $this->error = '没有文件被上传!';
  297. break;
  298. case 6:
  299. $this->error = '找不到临时文件夹!';
  300. break;
  301. case 7:
  302. $this->error = '文件写入失败!';
  303. break;
  304. default:
  305. $this->error = '未知上传错误!';
  306. }
  307. }
  308. /**
  309. * 检查文件大小是否合法
  310. * @param integer $size 数据
  311. */
  312. private function checkSize($size) {
  313. return !($size > $this->maxSize) || (0 == $this->maxSize);
  314. }
  315. /**
  316. * 检查上传的文件MIME类型是否合法
  317. * @param string $mime 数据
  318. */
  319. private function checkMime($mime) {
  320. return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->mimes);
  321. }
  322. /**
  323. * 检查上传的文件后缀是否合法
  324. * @param string $ext 后缀
  325. */
  326. private function checkExt($ext) {
  327. return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts);
  328. }
  329. /**
  330. * 根据上传文件命名规则取得保存文件名
  331. * @param string $file 文件信息
  332. */
  333. private function getSaveName($file) {
  334. $rule = $this->saveName;
  335. if (empty($rule)) { //保持文件名不变
  336. /* 解决pathinfo中文文件名BUG */
  337. $filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1);
  338. $savename = $filename;
  339. } else {
  340. $savename = $this->getName($rule, $file['name']);
  341. if(empty($savename)){
  342. $this->error = '文件命名规则错误!';
  343. return false;
  344. }
  345. }
  346. /* 文件保存后缀,支持强制更改文件后缀 */
  347. $ext = empty($this->config['saveExt']) ? $file['ext'] : $this->saveExt;
  348. return $savename . '.' . $ext;
  349. }
  350. /**
  351. * 获取子目录的名称
  352. * @param array $file 上传的文件信息
  353. */
  354. private function getSubPath($filename) {
  355. $subpath = '';
  356. $rule = $this->subName;
  357. if ($this->autoSub && !empty($rule)) {
  358. $subpath = $this->getName($rule, $filename) . '/';
  359. if(!empty($subpath) && !$this->uploader->mkdir($this->savePath . $subpath)){
  360. $this->error = $this->uploader->getError();
  361. return false;
  362. }
  363. }
  364. return $subpath;
  365. }
  366. /**
  367. * 根据指定的规则获取文件或目录名称
  368. * @param array $rule 规则
  369. * @param string $filename 原文件名
  370. * @return string 文件或目录名称
  371. */
  372. private function getName($rule, $filename){
  373. $name = '';
  374. if(is_array($rule)){ //数组规则
  375. $func = $rule[0];
  376. $param = (array)$rule[1];
  377. foreach ($param as &$value) {
  378. $value = str_replace('__FILE__', $filename, $value);
  379. }
  380. $name = call_user_func_array($func, $param);
  381. } elseif (is_string($rule)){ //字符串规则
  382. if(function_exists($rule)){
  383. $name = call_user_func($rule);
  384. } else {
  385. $name = $rule;
  386. }
  387. }
  388. return $name;
  389. }
  390. }