PageRenderTime 86ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/ThinkPHP/Library/Think/Upload.class.php

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