PageRenderTime 40ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/var/Widget/Upload.php

https://gitlab.com/wuhang2003/typecho
PHP | 432 lines | 392 code | 8 blank | 32 comment | 6 complexity | e08570b32c50673eee4d021b1f891bbd MD5 | raw file
  1. <?php
  2. if (!defined('__TYPECHO_ROOT_DIR__')) exit;
  3. /**
  4. * 上传动作
  5. *
  6. * @category typecho
  7. * @package Widget
  8. * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
  9. * @license GNU General Public License 2.0
  10. * @version $Id$
  11. */
  12. /**
  13. * 上传组件
  14. *
  15. * @author qining
  16. * @category typecho
  17. * @package Widget
  18. */
  19. class Widget_Upload extends Widget_Abstract_Contents implements Widget_Interface_Do
  20. {
  21. //上传文件目录
  22. const UPLOAD_DIR = '/usr/uploads';
  23. /**
  24. * 创建上传路径
  25. *
  26. * @access private
  27. * @param string $path 路径
  28. * @return boolean
  29. */
  30. private static function makeUploadDir($path)
  31. {
  32. $path = preg_replace("/\\\+/", '/', $path);
  33. $current = rtrim($path, '/');
  34. $last = $current;
  35. while (!is_dir($current) && false !== strpos($path, '/')) {
  36. $last = $current;
  37. $current = dirname($current);
  38. }
  39. if ($last == $current) {
  40. return true;
  41. }
  42. if (!@mkdir($last)) {
  43. return false;
  44. }
  45. $stat = @stat($last);
  46. $perms = $stat['mode'] & 0007777;
  47. @chmod($last, $perms);
  48. return self::makeUploadDir($path);
  49. }
  50. /**
  51. * 获取安全的文件名
  52. *
  53. * @param string $name
  54. * @static
  55. * @access private
  56. * @return string
  57. */
  58. private static function getSafeName(&$name)
  59. {
  60. $name = str_replace(array('"', '<', '>'), '', $name);
  61. $name = str_replace('\\', '/', $name);
  62. $name = false === strpos($name, '/') ? ('a' . $name) : str_replace('/', '/a', $name);
  63. $info = pathinfo($name);
  64. $name = substr($info['basename'], 1);
  65. return isset($info['extension']) ? strtolower($info['extension']) : '';
  66. }
  67. /**
  68. * 上传文件处理函数,如果需要实现自己的文件哈希或者特殊的文件系统,请在options表里把uploadHandle改成自己的函数
  69. *
  70. * @access public
  71. * @param array $file 上传的文件
  72. * @return mixed
  73. */
  74. public static function uploadHandle($file)
  75. {
  76. if (empty($file['name'])) {
  77. return false;
  78. }
  79. $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasUploaded)->uploadHandle($file);
  80. if ($hasUploaded) {
  81. return $result;
  82. }
  83. $ext = self::getSafeName($file['name']);
  84. if (!self::checkFileType($ext) || Typecho_Common::isAppEngine()) {
  85. return false;
  86. }
  87. $options = Typecho_Widget::widget('Widget_Options');
  88. $date = new Typecho_Date($options->gmtTime);
  89. $path = Typecho_Common::url(defined('__TYPECHO_UPLOAD_DIR__') ? __TYPECHO_UPLOAD_DIR__ : self::UPLOAD_DIR,
  90. defined('__TYPECHO_UPLOAD_ROOT_DIR__') ? __TYPECHO_UPLOAD_ROOT_DIR__ : __TYPECHO_ROOT_DIR__)
  91. . '/' . $date->year . '/' . $date->month;
  92. //创建上传目录
  93. if (!is_dir($path)) {
  94. if (!self::makeUploadDir($path)) {
  95. return false;
  96. }
  97. }
  98. //获取文件名
  99. $fileName = sprintf('%u', crc32(uniqid())) . '.' . $ext;
  100. $path = $path . '/' . $fileName;
  101. if (isset($file['tmp_name'])) {
  102. //移动上传文件
  103. if (!@move_uploaded_file($file['tmp_name'], $path)) {
  104. return false;
  105. }
  106. } else if (isset($file['bytes'])) {
  107. //直接写入文件
  108. if (!file_put_contents($path, $file['bytes'])) {
  109. return false;
  110. }
  111. } else {
  112. return false;
  113. }
  114. if (!isset($file['size'])) {
  115. $file['size'] = filesize($path);
  116. }
  117. //返回相对存储路径
  118. return array(
  119. 'name' => $file['name'],
  120. 'path' => (defined('__TYPECHO_UPLOAD_DIR__') ? __TYPECHO_UPLOAD_DIR__ : self::UPLOAD_DIR)
  121. . '/' . $date->year . '/' . $date->month . '/' . $fileName,
  122. 'size' => $file['size'],
  123. 'type' => $ext,
  124. 'mime' => Typecho_Common::mimeContentType($path)
  125. );
  126. }
  127. /**
  128. * 修改文件处理函数,如果需要实现自己的文件哈希或者特殊的文件系统,请在options表里把modifyHandle改成自己的函数
  129. *
  130. * @access public
  131. * @param array $content 老文件
  132. * @param array $file 新上传的文件
  133. * @return mixed
  134. */
  135. public static function modifyHandle($content, $file)
  136. {
  137. if (empty($file['name'])) {
  138. return false;
  139. }
  140. $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasModified)->modifyHandle($content, $file);
  141. if ($hasModified) {
  142. return $result;
  143. }
  144. $ext = self::getSafeName($file['name']);
  145. if ($content['attachment']->type != $ext || Typecho_Common::isAppEngine()) {
  146. return false;
  147. }
  148. $path = Typecho_Common::url($content['attachment']->path,
  149. defined('__TYPECHO_UPLOAD_ROOT_DIR__') ? __TYPECHO_UPLOAD_ROOT_DIR__ : __TYPECHO_ROOT_DIR__);
  150. $dir = dirname($path);
  151. //创建上传目录
  152. if (!is_dir($dir)) {
  153. if (!self::makeUploadDir($dir)) {
  154. return false;
  155. }
  156. }
  157. if (isset($file['tmp_name'])) {
  158. @unlink($path);
  159. //移动上传文件
  160. if (!@move_uploaded_file($file['tmp_name'], $path)) {
  161. return false;
  162. }
  163. } else if (isset($file['bytes'])) {
  164. @unlink($path);
  165. //直接写入文件
  166. if (!file_put_contents($path, $file['bytes'])) {
  167. return false;
  168. }
  169. } else {
  170. return false;
  171. }
  172. if (!isset($file['size'])) {
  173. $file['size'] = filesize($path);
  174. }
  175. //返回相对存储路径
  176. return array(
  177. 'name' => $content['attachment']->name,
  178. 'path' => $content['attachment']->path,
  179. 'size' => $file['size'],
  180. 'type' => $content['attachment']->type,
  181. 'mime' => $content['attachment']->mime
  182. );
  183. }
  184. /**
  185. * 删除文件
  186. *
  187. * @access public
  188. * @param array $content 文件相关信息
  189. * @return string
  190. */
  191. public static function deleteHandle(array $content)
  192. {
  193. $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasDeleted)->deleteHandle($content);
  194. if ($hasDeleted) {
  195. return $result;
  196. }
  197. return !Typecho_Common::isAppEngine()
  198. && @unlink(__TYPECHO_ROOT_DIR__ . '/' . $content['attachment']->path);
  199. }
  200. /**
  201. * 获取实际文件绝对访问路径
  202. *
  203. * @access public
  204. * @param array $content 文件相关信息
  205. * @return string
  206. */
  207. public static function attachmentHandle(array $content)
  208. {
  209. $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasPlugged)->attachmentHandle($content);
  210. if ($hasPlugged) {
  211. return $result;
  212. }
  213. $options = Typecho_Widget::widget('Widget_Options');
  214. return Typecho_Common::url($content['attachment']->path,
  215. defined('__TYPECHO_UPLOAD_URL__') ? __TYPECHO_UPLOAD_URL__ : $options->siteUrl);
  216. }
  217. /**
  218. * 获取实际文件数据
  219. *
  220. * @access public
  221. * @param array $content
  222. * @return string
  223. */
  224. public static function attachmentDataHandle(array $content)
  225. {
  226. $result = Typecho_Plugin::factory('Widget_Upload')->trigger($hasPlugged)->attachmentDataHandle($content);
  227. if ($hasPlugged) {
  228. return $result;
  229. }
  230. return file_get_contents(Typecho_Common::url($content['attachment']->path,
  231. defined('__TYPECHO_UPLOAD_ROOT_DIR__') ? __TYPECHO_UPLOAD_ROOT_DIR__ : __TYPECHO_ROOT_DIR__));
  232. }
  233. /**
  234. * 检查文件名
  235. *
  236. * @access private
  237. * @param string $ext 扩展名
  238. * @return boolean
  239. */
  240. public static function checkFileType($ext)
  241. {
  242. $options = Typecho_Widget::widget('Widget_Options');
  243. return in_array($ext, $options->allowedAttachmentTypes);
  244. }
  245. /**
  246. * 执行升级程序
  247. *
  248. * @access public
  249. * @return void
  250. */
  251. public function upload()
  252. {
  253. if (!empty($_FILES)) {
  254. $file = array_pop($_FILES);
  255. if (0 == $file['error'] && is_uploaded_file($file['tmp_name'])) {
  256. // xhr的send无法支持utf8
  257. if ($this->request->isAjax()) {
  258. $file['name'] = urldecode($file['name']);
  259. }
  260. $result = self::uploadHandle($file);
  261. if (false !== $result) {
  262. $this->pluginHandle()->beforeUpload($result);
  263. $struct = array(
  264. 'title' => $result['name'],
  265. 'slug' => $result['name'],
  266. 'type' => 'attachment',
  267. 'status' => 'publish',
  268. 'text' => serialize($result),
  269. 'allowComment' => 1,
  270. 'allowPing' => 0,
  271. 'allowFeed' => 1
  272. );
  273. if (isset($this->request->cid)) {
  274. $cid = $this->request->filter('int')->cid;
  275. if ($this->isWriteable($this->db->sql()->where('cid = ?', $cid))) {
  276. $struct['parent'] = $cid;
  277. }
  278. }
  279. $insertId = $this->insert($struct);
  280. $this->db->fetchRow($this->select()->where('table.contents.cid = ?', $insertId)
  281. ->where('table.contents.type = ?', 'attachment'), array($this, 'push'));
  282. /** 增加插件接口 */
  283. $this->pluginHandle()->upload($this);
  284. $this->response->throwJson(array($this->attachment->url, array(
  285. 'cid' => $insertId,
  286. 'title' => $this->attachment->name,
  287. 'type' => $this->attachment->type,
  288. 'size' => $this->attachment->size,
  289. 'bytes' => number_format(ceil($this->attachment->size / 1024)) . ' Kb',
  290. 'isImage' => $this->attachment->isImage,
  291. 'url' => $this->attachment->url,
  292. 'permalink' => $this->permalink
  293. )));
  294. }
  295. }
  296. }
  297. $this->response->throwJson(false);
  298. }
  299. /**
  300. * 执行升级程序
  301. *
  302. * @access public
  303. * @return void
  304. */
  305. public function modify()
  306. {
  307. if (!empty($_FILES)) {
  308. $file = array_pop($_FILES);
  309. if (0 == $file['error'] && is_uploaded_file($file['tmp_name'])) {
  310. $this->db->fetchRow($this->select()->where('table.contents.cid = ?', $this->request->filter('int')->cid)
  311. ->where('table.contents.type = ?', 'attachment'), array($this, 'push'));
  312. if (!$this->have()) {
  313. $this->response->setStatus(404);
  314. exit;
  315. }
  316. if (!$this->allow('edit')) {
  317. $this->response->setStatus(403);
  318. exit;
  319. }
  320. // xhr的send无法支持utf8
  321. if ($this->request->isAjax()) {
  322. $file['name'] = urldecode($file['name']);
  323. }
  324. $result = self::modifyHandle($this->row, $file);
  325. if (false !== $result) {
  326. $this->pluginHandle()->beforeModify($result);
  327. $this->update(array(
  328. 'text' => serialize($result)
  329. ), $this->db->sql()->where('cid = ?', $this->cid));
  330. $this->db->fetchRow($this->select()->where('table.contents.cid = ?', $this->cid)
  331. ->where('table.contents.type = ?', 'attachment'), array($this, 'push'));
  332. /** 增加插件接口 */
  333. $this->pluginHandle()->modify($this);
  334. $this->response->throwJson(array($this->attachment->url, array(
  335. 'cid' => $this->cid,
  336. 'title' => $this->attachment->name,
  337. 'type' => $this->attachment->type,
  338. 'size' => $this->attachment->size,
  339. 'bytes' => number_format(ceil($this->attachment->size / 1024)) . ' Kb',
  340. 'isImage' => $this->attachment->isImage,
  341. 'url' => $this->attachment->url,
  342. 'permalink' => $this->permalink
  343. )));
  344. }
  345. }
  346. }
  347. $this->response->throwJson(false);
  348. }
  349. /**
  350. * 初始化函数
  351. *
  352. * @access public
  353. * @return void
  354. */
  355. public function action()
  356. {
  357. if ($this->user->pass('contributor', true) && $this->request->isPost()) {
  358. $this->security->protect();
  359. if ($this->request->is('do=modify&cid')) {
  360. $this->modify();
  361. } else {
  362. $this->upload();
  363. }
  364. } else {
  365. $this->response->setStatus(403);
  366. }
  367. }
  368. }