Upload.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. <?php
  2. // +---------------------------------------------------------------------
  3. // | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
  4. // +---------------------------------------------------------------------
  5. // | Copyright (c) 2013-2014 http://www.thinkcmf.com All rights reserved.
  6. // +---------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +---------------------------------------------------------------------
  9. // | Author: Dean <zxxjjforever@163.com>
  10. // +---------------------------------------------------------------------
  11. namespace cmf\lib;
  12. use huo\model\agent\AssetModel;
  13. use think\File;
  14. /**
  15. * ThinkCMF上传类,分块上传
  16. */
  17. class Upload {
  18. private $request;
  19. private $error = false;
  20. private $fileType;
  21. private $formName = 'file';
  22. public function __construct() {
  23. $this->request = request();
  24. }
  25. public function getError() {
  26. return $this->error;
  27. }
  28. public function setFileType($fileType) {
  29. $this->fileType = $fileType;
  30. }
  31. public function setFormName($name) {
  32. $this->formName = $name;
  33. }
  34. public function upload($agent_id = 0, $to_file_path = '') {
  35. $uploadSetting = cmf_get_upload_setting();
  36. $arrFileTypes = [
  37. 'image' => ['title' => 'Image files', 'extensions' => $uploadSetting['file_types']['image']['extensions']],
  38. 'video' => ['title' => 'Video files', 'extensions' => $uploadSetting['file_types']['video']['extensions']],
  39. 'audio' => ['title' => 'Audio files', 'extensions' => $uploadSetting['file_types']['audio']['extensions']],
  40. 'file' => ['title' => 'Custom files', 'extensions' => $uploadSetting['file_types']['file']['extensions']]
  41. ];
  42. $arrData = $this->request->param();
  43. if (empty($arrData["filetype"])) {
  44. $arrData["filetype"] = "image";
  45. }
  46. $fileType = $this->fileType;
  47. if (empty($this->fileType)) {
  48. $fileType = $arrData["filetype"];
  49. }
  50. if (array_key_exists($arrData["filetype"], $arrFileTypes)) {
  51. $extensions = $uploadSetting['file_types'][$arrData["filetype"]]['extensions'];
  52. $fileTypeUploadMaxFileSize = $uploadSetting['file_types'][$fileType]['upload_max_filesize'];
  53. } else {
  54. $this->error = '上传文件类型配置错误!';
  55. return false;
  56. }
  57. //$strPostMaxSize = ini_get("post_max_size");
  58. //$strUploadMaxFileSize = ini_get("upload_max_filesize");
  59. /**
  60. * 断点续传 need
  61. */
  62. header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  63. header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
  64. header("Cache-Control: no-store, no-cache, must-revalidate");
  65. header("Cache-Control: post-check=0, pre-check=0", false);
  66. header("Pragma: no-cache");
  67. header("Access-Control-Allow-Origin: *"); // Support CORS
  68. if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { // other CORS headers if any...
  69. exit; // finish preflight CORS requests here
  70. }
  71. @set_time_limit(24 * 60 * 60);
  72. $cleanupTargetDir = false; // Remove old files
  73. $maxFileAge = 5 * 3600; // Temp file age in seconds
  74. /**
  75. * 断点续传 end
  76. */
  77. $app = $this->request->param('app');
  78. if (empty($app) || !file_exists(APP_PATH.$app)) {
  79. $app = 'default';
  80. }
  81. $fileImage = $this->request->file($this->formName);
  82. $originalName = $fileImage->getInfo('name');
  83. $arrAllowedExtensions = explode(',', $arrFileTypes[$fileType]['extensions']);
  84. $strFileExtension = strtolower(cmf_get_file_extension($originalName));
  85. if (!in_array($strFileExtension, $arrAllowedExtensions) || $strFileExtension == 'php') {
  86. $this->error = "非法文件类型!";
  87. return false;
  88. }
  89. $fileUploadMaxFileSize = $uploadSetting['upload_max_filesize'][$strFileExtension];
  90. $fileUploadMaxFileSize = empty($fileUploadMaxFileSize) ? 2097152 : $fileUploadMaxFileSize;//默认2M
  91. $strWebPath = "";//"upload" . DS;
  92. $strId = $this->request->post("id");
  93. $strDate = date('Ymd');
  94. $adminId = empty($agent_id) ? $agent_id : cmf_get_current_admin_id();
  95. $userId = cmf_get_current_user_id();
  96. $userId = empty($adminId) ? $userId : $adminId;
  97. $targetDir = RUNTIME_PATH."upload".DS.$userId.DS; // 断点续传 need
  98. if (!file_exists($targetDir)) {
  99. mkdir($targetDir, 0777, true);
  100. }
  101. /**
  102. * 断点续传 need
  103. */
  104. $strFilePath = md5($originalName);
  105. $chunk = $this->request->param("chunk", 0, "intval");
  106. $chunks = $this->request->param("chunks", 1, "intval");
  107. if (!$fileImage->isValid()) {
  108. $this->error = "非法文件!";
  109. return false;
  110. }
  111. if ($cleanupTargetDir) {
  112. if (!is_dir($targetDir) || !$dir = opendir($targetDir)) {
  113. $this->error = "Failed to open temp directory!";
  114. return false;
  115. }
  116. while (($file = readdir($dir)) !== false) {
  117. $tmpFilePath = $targetDir.$file;
  118. if ($tmpFilePath == "{$strFilePath}_{$chunk}.part"
  119. || $tmpFilePath == "{$strFilePath}_{$chunk}.parttmp") {
  120. continue;
  121. }
  122. if (preg_match('/\.(part|parttmp)$/', $file) && (@filemtime($tmpFilePath) < time() - $maxFileAge)) {
  123. @unlink($tmpFilePath);
  124. }
  125. }
  126. closedir($dir);
  127. }
  128. // Open temp file
  129. if (!$out = @fopen($targetDir."{$strFilePath}_{$chunk}.parttmp", "wb")) {
  130. $this->error = "上传文件临时目录不可写".$targetDir;
  131. return false;
  132. }
  133. // Read binary input stream and append it to temp file
  134. if (!$in = @fopen($fileImage->getInfo("tmp_name"), "rb")) {
  135. $this->error = "Failed to open input stream!";
  136. return false;
  137. }
  138. while ($buff = fread($in, 4096)) {
  139. fwrite($out, $buff);
  140. }
  141. @fclose($out);
  142. @fclose($in);
  143. rename($targetDir."{$strFilePath}_{$chunk}.parttmp", $targetDir."{$strFilePath}_{$chunk}.part");
  144. $done = true;
  145. for ($index = 0; $index < $chunks; $index++) {
  146. if (!file_exists($targetDir."{$strFilePath}_{$index}.part")) {
  147. $done = false;
  148. break;
  149. }
  150. }
  151. if (!$done) {
  152. die('');//分片没上传完
  153. }
  154. $fileSaveName = (empty($app) ? '' : $app.'/').$strDate.'/'.md5(uniqid()).".".$strFileExtension;
  155. $strSaveFilePath = CMF_ROOT.'public/upload/'.$fileSaveName; //TODO 测试 windows 下
  156. $strSaveFileDir = dirname($strSaveFilePath);
  157. if (!file_exists($strSaveFileDir)) {
  158. mkdir($strSaveFileDir, 0777, true);
  159. }
  160. // 合并临时文件
  161. if (!$out = @fopen($strSaveFilePath, "wb")) {
  162. $this->error = "上传目录不可写";
  163. return false;
  164. }
  165. if (flock($out, LOCK_EX)) {
  166. for ($index = 0; $index < $chunks; $index++) {
  167. if (!$in = @fopen($targetDir."{$strFilePath}_{$index}.part", "rb")) {
  168. break;
  169. }
  170. while ($buff = fread($in, 4096)) {
  171. fwrite($out, $buff);
  172. }
  173. fclose($in);
  174. unlink("{$targetDir}{$strFilePath}_{$index}.part");
  175. }
  176. flock($out, LOCK_UN);
  177. }
  178. @fclose($out);
  179. $fileImage = new File($strSaveFilePath, 'r');
  180. $arrInfo = [
  181. "name" => $originalName,
  182. "type" => $fileImage->getMime(),
  183. "tmp_name" => $strSaveFilePath,
  184. "error" => 0,
  185. "size" => $fileImage->getSize(),
  186. ];
  187. $fileImage->setSaveName($fileSaveName);
  188. $fileImage->setUploadInfo($arrInfo);
  189. /**
  190. * 断点续传 end
  191. */
  192. if (!$fileImage->validate(['size' => $fileUploadMaxFileSize])->check()) {
  193. $error = $fileImage->getError();
  194. unset($fileImage);
  195. unlink($strSaveFilePath);
  196. $this->error = $error;
  197. return false;
  198. }
  199. // $url=$first['url'];
  200. $storageSetting = cmf_get_cmf_settings('storage');
  201. $qiniuSetting = $storageSetting['Qiniu']['setting'];
  202. //$url=preg_replace('/^https/', $qiniu_setting['protocol'], $url);
  203. //$url=preg_replace('/^http/', $qiniu_setting['protocol'], $url);
  204. $arrInfo = [];
  205. if (config('FILE_UPLOAD_TYPE') == 'Qiniu' && $qiniuSetting['enable_picture_protect']) {
  206. //todo qiniu code ...
  207. // $previewUrl = $url.$qiniuSetting['style_separator'].$qiniuSetting['styles']['thumbnail300x300'];
  208. // $url= $url.$qiniuSetting['style_separator'].$qiniuSetting['styles']['watermark'];
  209. } else {
  210. if (empty($fileImage)) {
  211. $this->error = $fileImage->getError();
  212. return false;
  213. } else {
  214. $arrInfo["user_id"] = $userId;
  215. $arrInfo["file_size"] = $fileImage->getSize();
  216. $arrInfo["create_time"] = time();
  217. $arrInfo["file_md5"] = md5_file($strSaveFilePath);
  218. $arrInfo["file_sha1"] = sha1_file($strSaveFilePath);
  219. $arrInfo["file_key"] = $arrInfo["file_md5"].md5($arrInfo["file_sha1"]);
  220. $arrInfo["filename"] = $fileImage->getInfo("name");
  221. $arrInfo["file_path"] = $strWebPath.$fileSaveName;
  222. $arrInfo["suffix"] = $fileImage->getExtension();
  223. }
  224. }
  225. /* favicon.ico 图标上传特殊处理 名称为favicon.ico 并且应用为admin 替换 */
  226. if ('favicon.ico' == $fileImage->getInfo('name') && 'admin' == $app) {
  227. @copy($strSaveFilePath, CMF_ROOT.'public/admin/favicon.ico');
  228. @copy($strSaveFilePath, CMF_ROOT.'public/h5/favicon.ico');
  229. @copy($strSaveFilePath, CMF_ROOT.'public/h5m/favicon.ico');
  230. @copy($strSaveFilePath, CMF_ROOT.'public/agent/favicon.ico');
  231. @copy($strSaveFilePath, CMF_ROOT.'public/mobile/favicon.ico');
  232. @copy($strSaveFilePath, CMF_ROOT.'public/web/favicon.ico');
  233. @rename($strSaveFilePath, './favicon.ico');
  234. $fileImage = null;
  235. return [
  236. 'filepath' => '/favicon.ico',
  237. "name" => 'favicon.ico',
  238. 'id' => $strId,
  239. 'preview_url' => '/favicon.ico?'.rand(1, 9999),
  240. 'url' => '/favicon.ico',
  241. ];
  242. }
  243. /* icon-logo.png 图标上传特殊处理,SDK浮点图标 名称为icon-logo.png 并且应用为admin 替换 */
  244. if ('icon-logo.png' == $fileImage->getInfo('name') && 'admin' == $app) {
  245. @copy($strSaveFilePath, CMF_ROOT.'public/static/h5sdk/images/icon-logo.png');
  246. @rename($strSaveFilePath, CMF_ROOT.'public/static/sdk/images/icon-logo.png');
  247. $fileImage = null;
  248. return [
  249. 'filepath' => '/h5sdk/images/icon-logo.png',
  250. "name" => 'icon-logo.png',
  251. 'id' => $strId,
  252. 'preview_url' => STATICSITE.'/h5sdk/images/icon-logo.png?'.rand(1, 9999),
  253. 'url' => '/h5sdk/images/icon-logo.png',
  254. ];
  255. }
  256. //关闭文件对象
  257. $fileImage = null;
  258. //检查文件是否已经存在
  259. $assetModel = new AssetModel();
  260. $objAsset = $assetModel->where(["user_id" => $userId, "file_key" => $arrInfo["file_key"]])->find();
  261. $storage = cmf_get_option('storage');
  262. if (empty($storage['type'])) {
  263. $storage['type'] = 'Local';
  264. }
  265. $needUploadToRemoteStorage = false;//是否要上传到云存储
  266. if ($objAsset && $storage['type'] == 'Local') {
  267. $arrAsset = $objAsset->toArray();
  268. //$arrInfo["url"] = $this->request->domain() . $arrAsset["file_path"];
  269. $arrInfo["file_path"] = $arrAsset["file_path"];
  270. if (file_exists(CMF_ROOT.'public/upload/'.$arrInfo["file_path"])) {
  271. @unlink($strSaveFilePath); // 删除已经上传的文件
  272. } else {
  273. $oldFileDir = dirname(CMF_ROOT.'public/upload/'.$arrInfo["file_path"]);
  274. if (!file_exists($oldFileDir)) {
  275. mkdir($oldFileDir, 0777, true);
  276. }
  277. if (!empty($to_file_path)) {
  278. @rename($strSaveFilePath, $to_file_path);
  279. } else {
  280. @rename($strSaveFilePath, CMF_ROOT.'public/upload/'.$arrInfo["file_path"]);
  281. }
  282. }
  283. } else {
  284. $needUploadToRemoteStorage = true;
  285. $assetModel->data($arrInfo)->allowField(true)->save();
  286. }
  287. //删除临时文件
  288. // for ($index = 0; $index < $chunks; $index++) {
  289. // // echo $targetDir . "{$strFilePath}_{$index}.part";
  290. // @unlink($targetDir . "{$strFilePath}_{$index}.part");
  291. // }
  292. @rmdir($targetDir);
  293. if ($storage['type'] != 'Local') { // 增加存储驱动
  294. $watermark = cmf_get_plugin_config($storage['type']);
  295. $storage = new Storage($storage['type'], $storage['storages'][$storage['type']]);
  296. if ($needUploadToRemoteStorage) {
  297. session_write_close();
  298. $result = $storage->upload($arrInfo["file_path"], CMF_ROOT.'public/upload/'.$arrInfo["file_path"], $fileType);
  299. if (!empty($result)) {
  300. return array_merge(
  301. [
  302. 'filepath' => $arrInfo["file_path"],
  303. "name" => $arrInfo["filename"],
  304. "file_md5" => $arrInfo["file_md5"],
  305. 'id' => $strId,
  306. 'preview_url' => STATICSITE.'/upload/'.$arrInfo["file_path"],
  307. 'url' => STATICSITE.'/upload/'.$arrInfo["file_path"],
  308. ], $result
  309. );
  310. }
  311. } else {
  312. $previewUrl = $fileType == 'image' ? $storage->getPreviewUrl($arrInfo["file_path"])
  313. : $storage->getFileDownloadUrl($arrInfo["file_path"]);
  314. $url = $fileType == 'image' ? $storage->getImageUrl(
  315. $arrInfo["file_path"], $watermark['styles_watermark']
  316. ) : $storage->getFileDownloadUrl($arrInfo["file_path"]);
  317. //测试ing
  318. return [
  319. 'filepath' => $arrInfo["file_path"],
  320. "name" => $arrInfo["filename"],
  321. "file_md5" => $arrInfo["file_md5"],
  322. 'id' => $strId,
  323. 'preview_url' => $previewUrl,
  324. 'url' => $url,
  325. ];
  326. }
  327. }
  328. return [
  329. 'filepath' => $arrInfo["file_path"],
  330. "name" => $arrInfo["filename"],
  331. "file_md5" => $arrInfo["file_md5"],
  332. 'id' => $strId,
  333. 'preview_url' => STATICSITE.'/upload/'.$arrInfo["file_path"],
  334. 'url' => STATICSITE.'/upload/'.$arrInfo["file_path"],
  335. ];
  336. }
  337. }