Upload.class.php 15 KB

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