Uploader.class.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. <?php
  2. /**
  3. * Modify: caozha.com
  4. * 代码仓库: https://gitee.com/caozha/ueditor
  5. * User: Jinqn
  6. * Date: 5-11-2020
  7. * UEditor编辑器通用上传类
  8. * 新增水印图片
  9. */
  10. class Uploader {
  11. private $water; //是否添加水印(属性)
  12. private $fileField; //文件域名
  13. private $file; //文件上传对象
  14. private $base64; //文件上传对象
  15. private $config; //配置信息
  16. private $oriName; //原始文件名
  17. private $fileName; //新文件名
  18. private $fullName; //完整文件名,即从当前配置目录开始的URL
  19. private $filePath; //完整文件名,即从当前配置目录开始的URL
  20. private $fileSize; //文件大小
  21. private $fileType; //文件类型
  22. private $stateInfo; //上传状态信息,
  23. private $stateMap = array( //上传状态映射表,国际化用户需考虑此处数据的国际化
  24. "SUCCESS", //上传成功标记,在UEditor中内不可改变,否则flash判断会出错
  25. "文件大小超出 upload_max_filesize 限制",
  26. "文件大小超出 MAX_FILE_SIZE 限制",
  27. "文件未被完整上传",
  28. "没有文件被上传",
  29. "上传文件为空",
  30. "ERROR_TMP_FILE" => "临时文件错误",
  31. "ERROR_TMP_FILE_NOT_FOUND" => "找不到临时文件",
  32. "ERROR_SIZE_EXCEED" => "文件大小超出网站限制",
  33. "ERROR_TYPE_NOT_ALLOWED" => "文件类型不允许",
  34. "ERROR_CREATE_DIR" => "目录创建失败",
  35. "ERROR_DIR_NOT_WRITEABLE" => "目录没有写权限",
  36. "ERROR_FILE_MOVE" => "文件保存时出错",
  37. "ERROR_FILE_NOT_FOUND" => "找不到上传文件",
  38. "ERROR_WRITE_CONTENT" => "写入文件内容错误",
  39. "ERROR_UNKNOWN" => "未知错误",
  40. "ERROR_DEAD_LINK" => "链接不可用",
  41. "ERROR_HTTP_LINK" => "链接不是http链接",
  42. "ERROR_HTTP_CONTENTTYPE" => "链接contentType不正确"
  43. );
  44. /**
  45. * 构造函数
  46. * @param string $fileField 表单名称
  47. * @param array $config 配置项
  48. * @param string $type 处理文件上传的方式
  49. */
  50. public
  51. function __construct( $fileField, $config, $type = "upload", $watermark = false,$iswater ) {
  52. $this->fileField = $fileField;
  53. $this->config = $config;
  54. $this->type = $type;
  55. if(isset($iswater)){
  56. if($iswater==1){
  57. $this->water = true;
  58. }else{
  59. $this->water = false;
  60. }
  61. }else{
  62. $this->water = $watermark;
  63. }
  64. if ( $type == "remote" ) {
  65. $this->saveRemote();
  66. } else if ( $type == "base64" ) {
  67. $this->upBase64();
  68. } else {
  69. $this->upFile();
  70. }
  71. $this->stateMap[ 'ERROR_TYPE_NOT_ALLOWED' ] = mb_convert_encoding( $this->stateMap[ 'ERROR_TYPE_NOT_ALLOWED' ], 'utf-8', 'auto' );
  72. }
  73. /**
  74. * 上传文件的主处理方法
  75. * @return mixed
  76. */
  77. private
  78. function upFile() {
  79. $file = $this->file = $_FILES[ $this->fileField ];
  80. if ( !$file ) {
  81. $this->stateInfo = $this->getStateInfo( "ERROR_FILE_NOT_FOUND" );
  82. return;
  83. }
  84. if ( $this->file[ 'error' ] ) {
  85. $this->stateInfo = $this->getStateInfo( $file[ 'error' ] );
  86. return;
  87. } else if ( !file_exists( $file[ 'tmp_name' ] ) ) {
  88. $this->stateInfo = $this->getStateInfo( "ERROR_TMP_FILE_NOT_FOUND" );
  89. return;
  90. } else if ( !is_uploaded_file( $file[ 'tmp_name' ] ) ) {
  91. $this->stateInfo = $this->getStateInfo( "ERROR_TMPFILE" );
  92. return;
  93. }
  94. $this->oriName = $file[ 'name' ];
  95. $this->fileSize = $file[ 'size' ];
  96. $this->fileType = $this->getFileExt();
  97. $this->fullName = $this->getFullName();
  98. $this->filePath = $this->getFilePath();
  99. $this->fileName = $this->getFileName();
  100. $dirname = dirname( $this->filePath );
  101. //检查文件大小是否超出限制
  102. if ( !$this->checkSize() ) {
  103. $this->stateInfo = $this->getStateInfo( "ERROR_SIZE_EXCEED" );
  104. return;
  105. }
  106. //检查是否不允许的文件格式
  107. if ( !$this->checkType() ) {
  108. $this->stateInfo = $this->getStateInfo( "ERROR_TYPE_NOT_ALLOWED" );
  109. return;
  110. }
  111. //创建目录失败
  112. if ( !file_exists( $dirname ) && !mkdir( $dirname, 0777, true ) ) {
  113. $this->stateInfo = $this->getStateInfo( "ERROR_CREATE_DIR" );
  114. return;
  115. } else if ( !is_writeable( $dirname ) ) {
  116. $this->stateInfo = $this->getStateInfo( "ERROR_DIR_NOT_WRITEABLE" );
  117. return;
  118. }
  119. //移动文件
  120. if ( !( move_uploaded_file( $file[ "tmp_name" ], $this->filePath ) && file_exists( $this->filePath ) ) ) { //移动失败
  121. $this->stateInfo = $this->getStateInfo( "ERROR_FILE_MOVE" );
  122. } else { //移动成功
  123. $this->stateInfo = $this->stateMap[ 0 ];
  124. }
  125. if($this->water){//水印
  126. $this->watermark($this->filePath,$this->filePath);
  127. }
  128. }
  129. /**
  130. * 处理base64编码的图片上传
  131. * @return mixed
  132. */
  133. private
  134. function upBase64() {
  135. $base64Data = $_POST[ $this->fileField ];
  136. $img = base64_decode( $base64Data );
  137. $this->oriName = $this->config[ 'oriName' ];
  138. $this->fileSize = strlen( $img );
  139. $this->fileType = $this->getFileExt();
  140. $this->fullName = $this->getFullName();
  141. $this->filePath = $this->getFilePath();
  142. $this->fileName = $this->getFileName();
  143. $dirname = dirname( $this->filePath );
  144. //检查文件大小是否超出限制
  145. if ( !$this->checkSize() ) {
  146. $this->stateInfo = $this->getStateInfo( "ERROR_SIZE_EXCEED" );
  147. return;
  148. }
  149. //创建目录失败
  150. if ( !file_exists( $dirname ) && !mkdir( $dirname, 0777, true ) ) {
  151. $this->stateInfo = $this->getStateInfo( "ERROR_CREATE_DIR" );
  152. return;
  153. } else if ( !is_writeable( $dirname ) ) {
  154. $this->stateInfo = $this->getStateInfo( "ERROR_DIR_NOT_WRITEABLE" );
  155. return;
  156. }
  157. //移动文件
  158. if ( !( file_put_contents( $this->filePath, $img ) && file_exists( $this->filePath ) ) ) { //移动失败
  159. $this->stateInfo = $this->getStateInfo( "ERROR_WRITE_CONTENT" );
  160. } else { //移动成功
  161. $this->stateInfo = $this->stateMap[ 0 ];
  162. }
  163. }
  164. /**
  165. * 拉取远程图片
  166. * @return mixed
  167. */
  168. private
  169. function saveRemote() {
  170. $imgUrl = htmlspecialchars( $this->fileField );
  171. $imgUrl = str_replace( "&amp;", "&", $imgUrl );
  172. //获取带有GET参数的真实图片url路径
  173. $pathRes = parse_url( $imgUrl );
  174. $queryString = isset( $pathRes[ 'query' ] ) ? $pathRes[ 'query' ] : '';
  175. $imgUrl = str_replace( '?' . $queryString, '', $imgUrl );
  176. //http开头验证
  177. if ( strpos( $imgUrl, "http" ) !== 0 ) {
  178. $this->stateInfo = $this->getStateInfo( "ERROR_HTTP_LINK" );
  179. return;
  180. }
  181. //获取请求头并检测死链
  182. $heads = get_headers( $imgUrl, 1 );
  183. if ( !( stristr( $heads[ 0 ], "200" ) && stristr( $heads[ 0 ], "OK" ) ) ) {
  184. $this->stateInfo = $this->getStateInfo( "ERROR_DEAD_LINK" );
  185. return;
  186. }
  187. //格式验证(扩展名验证和Content-Type验证)
  188. $fileType = strtolower( strrchr( $imgUrl, '.' ) );
  189. if ( !in_array( $fileType, $this->config[ 'allowFiles' ] ) || !isset( $heads[ 'Content-Type' ] ) || !stristr( $heads[ 'Content-Type' ], "image" ) ) {
  190. $this->stateInfo = $this->getStateInfo( "ERROR_HTTP_CONTENTTYPE" );
  191. return;
  192. }
  193. //打开输出缓冲区并获取远程图片
  194. ob_start();
  195. $context = stream_context_create(
  196. array( 'http' => array(
  197. 'follow_location' => false // don't follow redirects
  198. ) )
  199. );
  200. readfile( $imgUrl . '?' . $queryString, false, $context );
  201. $img = ob_get_contents();
  202. ob_end_clean();
  203. preg_match( "/[\/]([^\/]*)[\.]?[^\.\/]*$/", $imgUrl, $m );
  204. $this->oriName = $m ? $m[ 1 ] : "";
  205. $this->fileSize = strlen( $img );
  206. $this->fileType = $this->getFileExt();
  207. $this->fullName = $this->getFullName();
  208. $this->filePath = $this->getFilePath();
  209. $this->fileName = $this->getFileName();
  210. $dirname = dirname( $this->filePath );
  211. //检查文件大小是否超出限制
  212. if ( !$this->checkSize() ) {
  213. $this->stateInfo = $this->getStateInfo( "ERROR_SIZE_EXCEED" );
  214. return;
  215. }
  216. //检查文件内容是否真的是图片
  217. if ( substr( mime_content_type( $this->filePath ), 0, 5 ) != 'image' ) {
  218. $this->stateInfo = $this->getStateInfo( "ERROR_TYPE_NOT_ALLOWED" );
  219. return;
  220. }
  221. //创建目录失败
  222. if ( !file_exists( $dirname ) && !mkdir( $dirname, 0777, true ) ) {
  223. $this->stateInfo = $this->getStateInfo( "ERROR_CREATE_DIR" );
  224. return;
  225. } else if ( !is_writeable( $dirname ) ) {
  226. $this->stateInfo = $this->getStateInfo( "ERROR_DIR_NOT_WRITEABLE" );
  227. return;
  228. }
  229. //移动文件
  230. if ( !( file_put_contents( $this->filePath, $img ) && file_exists( $this->filePath ) ) ) { //移动失败
  231. $this->stateInfo = $this->getStateInfo( "ERROR_WRITE_CONTENT" );
  232. } else { //移动成功
  233. $this->stateInfo = $this->stateMap[ 0 ];
  234. }
  235. }
  236. /**
  237. * 上传错误检查
  238. * @param $errCode
  239. * @return string
  240. */
  241. private
  242. function getStateInfo( $errCode ) {
  243. return !$this->stateMap[ $errCode ] ? $this->stateMap[ "ERROR_UNKNOWN" ] : $this->stateMap[ $errCode ];
  244. }
  245. /**
  246. * 获取文件扩展名
  247. * @return string
  248. */
  249. private
  250. function getFileExt() {
  251. return strtolower( strrchr( $this->oriName, '.' ) );
  252. }
  253. /**
  254. * 重命名文件
  255. * @return string
  256. */
  257. private
  258. function getFullName() {
  259. //替换日期事件
  260. $t = time();
  261. $d = explode( '-', date( "Y-y-m-d-H-i-s" ) );
  262. $format = $this->config[ "pathFormat" ];
  263. $format = str_replace( "{yyyy}", $d[ 0 ], $format );
  264. $format = str_replace( "{yy}", $d[ 1 ], $format );
  265. $format = str_replace( "{mm}", $d[ 2 ], $format );
  266. $format = str_replace( "{dd}", $d[ 3 ], $format );
  267. $format = str_replace( "{hh}", $d[ 4 ], $format );
  268. $format = str_replace( "{ii}", $d[ 5 ], $format );
  269. $format = str_replace( "{ss}", $d[ 6 ], $format );
  270. $format = str_replace( "{time}", $t, $format );
  271. //过滤文件名的非法字符,并替换文件名
  272. $oriName = substr( $this->oriName, 0, strrpos( $this->oriName, '.' ) );
  273. $oriName = preg_replace( "/[\|\?\"\<\>\/\*\\\\]+/", '', $oriName );
  274. $format = str_replace( "{filename}", $oriName, $format );
  275. //替换随机字符串
  276. $randNum = rand( 1, 10000000000 ) . rand( 1, 10000000000 );
  277. if ( preg_match( "/\{rand\:([\d]*)\}/i", $format, $matches ) ) {
  278. $format = preg_replace( "/\{rand\:[\d]*\}/i", substr( $randNum, 0, $matches[ 1 ] ), $format );
  279. }
  280. if ( $this->fileType ) {
  281. $ext = $this->fileType;
  282. } else {
  283. $ext = $this->getFileExt();
  284. }
  285. return $format . $ext;
  286. }
  287. /**
  288. * 获取文件名
  289. * @return string
  290. */
  291. private
  292. function getFileName() {
  293. return substr( $this->filePath, strrpos( $this->filePath, '/' ) + 1 );
  294. }
  295. /**
  296. * 获取文件完整路径
  297. * @return string
  298. */
  299. private
  300. function getFilePath() {
  301. $fullname = $this->fullName;
  302. $rootPath = $_SERVER[ 'DOCUMENT_ROOT' ];
  303. if ( substr( $fullname, 0, 1 ) != '/' ) {
  304. $fullname = '/' . $fullname;
  305. }
  306. return $rootPath . $fullname;
  307. }
  308. /**
  309. * 文件类型检测
  310. * @return bool
  311. */
  312. private
  313. function checkType() {
  314. return in_array( $this->getFileExt(), $this->config[ "allowFiles" ] );
  315. }
  316. /**
  317. * 文件大小检测
  318. * @return bool
  319. */
  320. private
  321. function checkSize() {
  322. return $this->fileSize <= ( $this->config[ "maxSize" ] );
  323. }
  324. /**
  325. * 获取当前上传成功文件的各项信息
  326. * @return array
  327. */
  328. public
  329. function getFileInfo() {
  330. return array(
  331. "state" => $this->stateInfo,
  332. "url" => $this->fullName,
  333. "title" => $this->fileName,
  334. "original" => $this->oriName,
  335. "type" => $this->fileType,
  336. "size" => $this->fileSize
  337. );
  338. }
  339. /*
  340. * 图片加水印
  341. * $source string 图片资源
  342. * $target string 添加水印后的名字
  343. * $w_pos int 水印位置安排(1-10)【1:左头顶;2:中间头顶;3:右头顶...值空:随机位置】
  344. * $w_img string 水印图片路径
  345. * $w_text string 显示的文字
  346. * $w_font int 字体大小
  347. * $w_color string 字体颜色
  348. */
  349. public function watermark($source, $target = '', $w_pos = '', $w_img = '', $w_text = 'caozha.com',$w_font = 10, $w_color = '#CC0000') {
  350. $this->w_img = 'water/watermark.png';//水印图片
  351. $this->w_pos = 9;
  352. $this->w_minwidth = 220;//最少宽度
  353. $this->w_minheight = 220;//最少高度
  354. $this->w_quality = 80;//图像质量
  355. $this->w_pct = 50;//透明度
  356. $w_pos = $w_pos ? $w_pos : $this->w_pos;
  357. $w_img = $w_img ? $w_img : $this->w_img;
  358. if(!$this->check($source)) return false;
  359. if(!$target) $target = $source;
  360. $source_info = getimagesize($source);//图片信息
  361. $source_w = $source_info[0];//图片宽度
  362. $source_h = $source_info[1];//图片高度
  363. if($source_w < $this->w_minwidth || $source_h < $this->w_minheight) return false;
  364. switch($source_info[2]) { //图片类型
  365. case 1 : //GIF格式
  366. $source_img = imagecreatefromgif($source);
  367. break;
  368. case 2 : //JPG格式
  369. $source_img = imagecreatefromjpeg($source);
  370. break;
  371. case 3 : //PNG格式
  372. $source_img = imagecreatefrompng($source);
  373. //imagealphablending($source_img,false); //关闭混色模式
  374. imagesavealpha($source_img,true); //设置标记以在保存 PNG 图像时保存完整的 alpha 通道信息(与单一透明色相反)
  375. break;
  376. default :
  377. return false;
  378. }
  379. if(!empty($w_img) && file_exists($w_img)) { //水印图片有效
  380. $ifwaterimage = 1; //标记
  381. $water_info = getimagesize($w_img);
  382. $width = $water_info[0];
  383. $height = $water_info[1];
  384. switch($water_info[2]) {
  385. case 1 :
  386. $water_img = imagecreatefromgif($w_img);
  387. break;
  388. case 2 :
  389. $water_img = imagecreatefromjpeg($w_img);
  390. break;
  391. case 3 :
  392. $water_img = imagecreatefrompng($w_img);
  393. imagealphablending($water_img,false);
  394. imagesavealpha($water_img,true);
  395. break;
  396. default :
  397. return;
  398. }
  399. }else{
  400. $ifwaterimage = 0;
  401. $temp = imagettfbbox(ceil($w_font*2.5), 0, './water/elephant.ttf', $w_text); //imagettfbbox返回一个含有 8 个单元的数组表示了文本外框的四个角
  402. $width = $temp[2] - $temp[6];
  403. $height = $temp[3] - $temp[7];
  404. unset($temp);
  405. }
  406. switch($w_pos) {
  407. case 1:
  408. $wx = 5;
  409. $wy = 5;
  410. break;
  411. case 2:
  412. $wx = ($source_w - $width) / 2;
  413. $wy = 0;
  414. break;
  415. case 3:
  416. $wx = $source_w - $width;
  417. $wy = 0;
  418. break;
  419. case 4:
  420. $wx = 0;
  421. $wy = ($source_h - $height) / 2;
  422. break;
  423. case 5:
  424. $wx = ($source_w - $width) / 2;
  425. $wy = ($source_h - $height) / 2;
  426. break;
  427. case 6:
  428. $wx = $source_w - $width;
  429. $wy = ($source_h - $height) / 2;
  430. break;
  431. case 7:
  432. $wx = 0;
  433. $wy = $source_h - $height;
  434. break;
  435. case 8:
  436. $wx = ($source_w - $width) / 2;
  437. $wy = $source_h - $height;
  438. break;
  439. case 9:
  440. $wx = $source_w - ($width+5);
  441. $wy = $source_h - ($height+5);
  442. break;
  443. case 10:
  444. $wx = rand(0,($source_w - $width));
  445. $wy = rand(0,($source_h - $height));
  446. break;
  447. default:
  448. $wx = rand(0,($source_w - $width));
  449. $wy = rand(0,($source_h - $height));
  450. break;
  451. }
  452. /*
  453. dst_im 目标图像
  454. src_im 被拷贝的源图像
  455. dst_x 目标图像开始 x 坐标
  456. dst_y 目标图像开始 y 坐标,x,y同为 0 则从左上角开始
  457. src_x 拷贝图像开始 x 坐标
  458. src_y 拷贝图像开始 y 坐标,x,y同为 0 则从左上角开始拷贝
  459. src_w (从 src_x 开始)拷贝的宽度
  460. src_h (从 src_y 开始)拷贝的高度
  461. pct 图像合并程度,取值 0-100 ,当 pct=0 时,实际上什么也没做,反之完全合并。
  462. */
  463. if($ifwaterimage) {
  464. if($water_info[2] == 3) {
  465. imagecopy($source_img, $water_img, $wx + 10, $wy , 0, 0, $width, $height);
  466. }else{
  467. imagecopymerge($source_img, $water_img, $wx, $wy, 0, 0, $width, $height, $this->w_pct);
  468. }
  469. }else{
  470. if(!empty($w_color) && (strlen($w_color)==7)) {
  471. $r = hexdec(substr($w_color,1,2));
  472. $g = hexdec(substr($w_color,3,2));
  473. $b = hexdec(substr($w_color,5));
  474. }else{
  475. return;
  476. }
  477. imagestring($source_img,$w_font,$wx,$wy,$w_text,imagecolorallocate($source_img,$r,$g,$b));
  478. }
  479. switch($source_info[2]) {
  480. case 1 :
  481. imagegif($source_img, $target);
  482. //GIF 格式将图像输出到浏览器或文件(欲输出的图像资源, 指定输出图像的文件名)
  483. break;
  484. case 2 :
  485. imagejpeg($source_img, $target, $this->w_quality);
  486. break;
  487. case 3 :
  488. imagepng($source_img, $target);
  489. break;
  490. default :
  491. return;
  492. }
  493. // $font = "./water/elephant.ttf";
  494. // $image->open($name)->text("caozha.com",$font,27,'#82797A',array(95,$height))->save($name);
  495. if(isset($water_info)){
  496. unset($water_info);
  497. }
  498. if(isset($water_img)) {
  499. imagedestroy($water_img);
  500. }
  501. unset($source_info);
  502. imagedestroy($source_img);
  503. return true;
  504. }
  505. public function check($image){
  506. return extension_loaded('gd') && preg_match("/\.(jpg|jpeg|gif|png)/i", $image, $m) && file_exists($image) && function_exists('imagecreatefrom'.($m[1] == 'jpg' ? 'jpeg' : $m[1]));
  507. }
  508. }