UeditorAction.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <?php
  2. /**
  3. * Author: lf
  4. * Blog: https://blog.feehi.com
  5. * Email: job@feehi.com
  6. * Created at: 2017-03-15 21:16
  7. */
  8. namespace backend\widgets\ueditor;
  9. use yii;
  10. use yii\imagine\Image;
  11. use yii\web\Response;
  12. class UeditorAction extends yii\base\Action
  13. {
  14. /**
  15. * UEditor的配置
  16. *
  17. * @see http://fex-team.github.io/ueditor/#start-config
  18. * @var array
  19. */
  20. public $config;
  21. /**
  22. * 列出文件/图片时需要忽略的文件夹
  23. * 主要用于处理缩略图管理,兼容比如elFinder之类的程序
  24. *
  25. * @var array
  26. */
  27. public $ignoreDir = [
  28. '.thumbnails'
  29. ];
  30. /**
  31. * 缩略图设置
  32. * 默认不开启
  33. * ['height' => 200, 'width' => 200]表示生成200*200的缩略图,如果设置为空数组则不生成缩略图
  34. *
  35. * @var array
  36. */
  37. public $thumbnail = [];
  38. /**
  39. * 图片缩放设置
  40. * 默认不缩放。
  41. * 配置如 ['height'=>200,'width'=>200]
  42. *
  43. * @var array
  44. */
  45. public $zoom = [];
  46. /**
  47. * 水印设置
  48. * 参考配置如下:
  49. * ['path'=>'水印图片位置','position'=>0]
  50. * 默认位置为 9,可不配置
  51. * position in [1 ,9],表示从左上到右下的9个位置。
  52. *
  53. * @var array
  54. */
  55. public $watermark = [];
  56. /**
  57. * 是否允许内网采集
  58. * 如果为 false 则远程图片获取不获取内网图片,防止 SSRF。
  59. * 默认为 false
  60. *
  61. * @var bool
  62. */
  63. public $allowIntranet = false;
  64. /**
  65. * 上传目录
  66. *
  67. * @var string
  68. */
  69. protected $uploadPath;
  70. public function init()
  71. {
  72. parent::init();
  73. //CSRF 基于 POST 验证,UEditor 无法添加自定义 POST 数据,同时由于这里不会产生安全问题,故简单粗暴地取消 CSRF 验证。
  74. //如需 CSRF 防御,可以使用 server_param 方法,然后在这里将 Get 的 CSRF 添加到 POST 的数组中。。。
  75. Yii::$app->request->enableCsrfValidation = false;
  76. //当客户使用低版本IE时,会使用swf上传插件,维持认证状态可以参考文档UEditor「自定义请求参数」部分。
  77. //http://fex.baidu.com/ueditor/#server-server_param
  78. //保留UE默认的配置引入方式
  79. if (file_exists(__DIR__ . '/config.json')) {
  80. $CONFIG = json_decode(preg_replace("/\/\*[\s\S]+?\*\//", '', file_get_contents(__DIR__ . '/config.json')), true);
  81. } else {
  82. $CONFIG = [];
  83. }
  84. if (! is_array($this->config)) {
  85. $this->config = [];
  86. }
  87. if (! is_array($CONFIG)) {
  88. $CONFIG = [];
  89. }
  90. $default = [
  91. 'imagePathFormat' => '/upload/image/{yyyy}{mm}{dd}/{time}{rand:8}',
  92. 'scrawlPathFormat' => '/upload/image/{yyyy}{mm}{dd}/{time}{rand:8}',
  93. 'snapscreenPathFormat' => '/upload/image/{yyyy}{mm}{dd}/{time}{rand:8}',
  94. 'catcherPathFormat' => '/upload/image/{yyyy}{mm}{dd}/{time}{rand:8}',
  95. 'videoPathFormat' => '/upload/video/{yyyy}{mm}{dd}/{time}{rand:8}',
  96. 'filePathFormat' => '/upload/file/{yyyy}{mm}{dd}/{rand:8}_{filename}',
  97. 'imageManagerListPath' => '/upload/image/',
  98. 'fileManagerListPath' => '/upload/file/',
  99. "imageUrlPrefix" => Yii::$app->params['site']['url'],
  100. 'scrawUrlPrefix' => Yii::$app->params['site']['url'],
  101. ];
  102. $this->config = $this->config + $default + $CONFIG;
  103. $this->uploadPath = Yii::getAlias('@frontend/web/uploads');
  104. if (! is_array($this->thumbnail)) {
  105. $this->thumbnail = false;
  106. }
  107. }
  108. /**
  109. * 统一后台入口
  110. */
  111. public function run()
  112. {
  113. $action = strtolower(Yii::$app->request->get('action', 'config'));
  114. $actions = [
  115. 'uploadimage' => 'uploadImage',
  116. 'uploadscrawl' => 'uploadScrawl',
  117. 'uploadvideo' => 'uploadVideo',
  118. 'uploadfile' => 'uploadFile',
  119. 'listimage' => 'listImage',
  120. 'listfile' => 'listFile',
  121. 'catchimage' => 'catchImage',
  122. 'config' => 'config',
  123. 'listinfo' => 'ListInfo'
  124. ];
  125. if (isset($actions[$action])) {
  126. Yii::$app->getResponse()->format = Response::FORMAT_JSON;
  127. return call_user_func_array([$this, 'action' . $actions[$action]], []);
  128. } else {
  129. return $this->show(['state' => 'Unknown action.']);
  130. }
  131. }
  132. /**
  133. * 显示配置信息
  134. */
  135. public function actionConfig()
  136. {
  137. return $this->show($this->config);
  138. }
  139. /**
  140. * 上传图片
  141. */
  142. public function actionUploadImage()
  143. {
  144. $config = [
  145. 'pathFormat' => $this->config['imagePathFormat'],
  146. 'maxSize' => $this->config['imageMaxSize'],
  147. 'allowFiles' => $this->config['imageAllowFiles']
  148. ];
  149. $fieldName = $this->config['imageFieldName'];
  150. $result = $this->upload($fieldName, $config);
  151. return $this->show($result);
  152. }
  153. /**
  154. * 上传涂鸦
  155. */
  156. public function actionUploadScrawl()
  157. {
  158. $config = [
  159. 'pathFormat' => $this->config['scrawlPathFormat'],
  160. 'maxSize' => $this->config['scrawlMaxSize'],
  161. 'allowFiles' => $this->config['scrawlAllowFiles'],
  162. 'oriName' => 'scrawl.png'
  163. ];
  164. $fieldName = $this->config['scrawlFieldName'];
  165. $result = $this->upload($fieldName, $config, 'base64');
  166. return $this->show($result);
  167. }
  168. /**
  169. * 上传视频
  170. */
  171. public function actionUploadVideo()
  172. {
  173. $config = [
  174. 'pathFormat' => $this->config['videoPathFormat'],
  175. 'maxSize' => $this->config['videoMaxSize'],
  176. 'allowFiles' => $this->config['videoAllowFiles']
  177. ];
  178. $fieldName = $this->config['videoFieldName'];
  179. $result = $this->upload($fieldName, $config);
  180. return $this->show($result);
  181. }
  182. /**
  183. * 上传文件
  184. */
  185. public function actionUploadFile()
  186. {
  187. $config = [
  188. 'pathFormat' => $this->config['filePathFormat'],
  189. 'maxSize' => $this->config['fileMaxSize'],
  190. 'allowFiles' => $this->config['fileAllowFiles']
  191. ];
  192. $fieldName = $this->config['fileFieldName'];
  193. $result = $this->upload($fieldName, $config);
  194. return $this->show($result);
  195. }
  196. /**
  197. * 文件列表
  198. */
  199. public function actionListFile()
  200. {
  201. $allowFiles = $this->config['fileManagerAllowFiles'];
  202. $listSize = $this->config['fileManagerListSize'];
  203. $path = $this->config['fileManagerListPath'];
  204. $result = $this->manage($allowFiles, $listSize, $path);
  205. return $this->show($result);
  206. }
  207. /**
  208. * 图片列表
  209. */
  210. public function actionListImage()
  211. {
  212. $allowFiles = $this->config['imageManagerAllowFiles'];
  213. $listSize = $this->config['imageManagerListSize'];
  214. $path = $this->config['imageManagerListPath'];
  215. $result = $this->manage($allowFiles, $listSize, $path);
  216. return $this->show($result);
  217. }
  218. /**
  219. * 获取远程图片
  220. */
  221. public function actionCatchImage()
  222. {
  223. /* 上传配置 */
  224. $config = [
  225. 'pathFormat' => $this->config['catcherPathFormat'],
  226. 'maxSize' => $this->config['catcherMaxSize'],
  227. 'allowFiles' => $this->config['catcherAllowFiles'],
  228. 'oriName' => 'remote.png'
  229. ];
  230. $fieldName = $this->config['catcherFieldName'];
  231. /* 抓取远程图片 */
  232. $list = [];
  233. if (isset($_POST[$fieldName])) {
  234. $source = $_POST[$fieldName];
  235. } else {
  236. $source = $_GET[$fieldName];
  237. }
  238. foreach ($source as $imgUrl) {
  239. $item = new Uploader($imgUrl, $config, 'remote');
  240. if ($this->allowIntranet) {
  241. $item->setAllowIntranet(true);
  242. }
  243. $info = $item->getFileInfo();
  244. $info['thumbnail'] = $this->imageHandle($info['url']);
  245. $list[] = [
  246. 'state' => $info['state'],
  247. 'url' => $info['url'],
  248. 'source' => $imgUrl
  249. ];
  250. }
  251. /* 返回抓取数据 */
  252. return [
  253. 'state' => count($list) ? 'SUCCESS' : 'ERROR',
  254. 'list' => $list
  255. ];
  256. }
  257. /**
  258. * 各种上传
  259. *
  260. * @param $fieldName
  261. * @param $config
  262. * @param $base64
  263. * @return array
  264. */
  265. protected function upload($fieldName, $config, $base64 = 'upload')
  266. {
  267. $up = new Uploader($fieldName, $config, $base64);
  268. if ($this->allowIntranet) {
  269. $up->setAllowIntranet(true);
  270. }
  271. $info = $up->getFileInfo();
  272. if (($this->thumbnail or $this->zoom or $this->watermark) && $info['state'] == 'SUCCESS' && in_array($info['type'], [
  273. '.png',
  274. '.jpg',
  275. '.bmp',
  276. '.gif'
  277. ])
  278. ) {
  279. $info['thumbnail'] = Yii::$app->request->baseUrl . $this->imageHandle($info['url']);
  280. }
  281. $info['original'] = htmlspecialchars($info['original']);
  282. $info['width'] = $info['height'] = 500;
  283. return $info;
  284. }
  285. /**
  286. * 自动处理图片
  287. *
  288. * @param $file
  289. * @return mixed|string
  290. */
  291. protected function imageHandle($file)
  292. {
  293. if (substr($file, 0, 1) != '/') {
  294. $file = '/' . $file;
  295. }
  296. //先处理缩略图
  297. if ($this->thumbnail && ! empty($this->thumbnail['height']) && ! empty($this->thumbnail['width'])) {
  298. $file = pathinfo($file);
  299. $file = $file['dirname'] . '/' . $file['filename'] . '.thumbnail.' . $file['extension'];
  300. Image::thumbnail($this->uploadPath . $file, intval($this->thumbnail['width']), intval($this->thumbnail['height']))
  301. ->save($this->uploadPath . $file);
  302. }
  303. //再处理缩放,默认不缩放
  304. //...缩放效果非常差劲-,-
  305. if (isset($this->zoom['height']) && isset($this->zoom['width'])) {
  306. $size = $this->getSize($this->uploadPath . $file);
  307. if ($size && $size[0] > 0 && $size[1] > 0) {
  308. $ratio = min([$this->zoom['height'] / $size[0], $this->zoom['width'] / $size[1], 1]);
  309. Image::thumbnail($this->uploadPath . $file, ceil($size[0] * $ratio), ceil($size[1] * $ratio))
  310. ->save($this->uploadPath . $file);
  311. }
  312. }
  313. //最后生成水印
  314. if (isset($this->watermark['path']) && file_exists($this->watermark['path'])) {
  315. if (! isset($this->watermark['position']) or $this->watermark['position'] > 9 or $this->watermark['position'] < 0 or ! is_numeric($this->watermark['position'])) {
  316. $this->watermark['position'] = 9;
  317. }
  318. $size = $this->getSize($this->uploadPath . $file);
  319. $waterSize = $this->getSize($this->watermark['path']);
  320. if ($size[0] > $waterSize[0] and $size[1] > $waterSize[1]) {
  321. $halfX = $size[0] / 2;
  322. $halfY = $size[1] / 2;
  323. $halfWaterX = $waterSize[0] / 2;
  324. $halfWaterY = $waterSize[1] / 2;
  325. switch (intval($this->watermark['position'])) {
  326. case 1:
  327. $x = 0;
  328. $y = 0;
  329. break;
  330. case 2:
  331. $x = $halfX - $halfWaterX;
  332. $y = 0;
  333. break;
  334. case 3:
  335. $x = $size[0] - $waterSize[0];
  336. $y = 0;
  337. break;
  338. case 4:
  339. $x = 0;
  340. $y = $halfY - $halfWaterY;
  341. break;
  342. case 5:
  343. $x = $halfX - $halfWaterX;
  344. $y = $halfY - $halfWaterY;
  345. break;
  346. case 6:
  347. $x = $size[0] - $waterSize[0];
  348. $y = $halfY - $halfWaterY;
  349. break;
  350. case 7:
  351. $x = 0;
  352. $y = $size[1] - $waterSize[1];
  353. break;
  354. case 8:
  355. $x = $halfX - $halfWaterX;
  356. $y = $size[1] - $waterSize[1];
  357. break;
  358. case 9:
  359. default:
  360. $x = $size[0] - $waterSize[0];
  361. $y = $size[1] - $waterSize[1];
  362. }
  363. Image::watermark($this->uploadPath . $file, $this->watermark['path'], [$x, $y])
  364. ->save($this->uploadPath . $file);
  365. }
  366. }
  367. return $file;
  368. }
  369. /**
  370. * 获取图片的大小
  371. * 主要用于获取图片大小并
  372. *
  373. * @param $file
  374. * @return array
  375. */
  376. protected function getSize($file)
  377. {
  378. if (! file_exists($file)) {
  379. return [];
  380. }
  381. $info = pathinfo($file);
  382. $image = null;
  383. switch (strtolower($info['extension'])) {
  384. case 'gif':
  385. $image = imagecreatefromgif($file);
  386. break;
  387. case 'jpg':
  388. case 'jpeg':
  389. $image = imagecreatefromjpeg($file);
  390. break;
  391. case 'png':
  392. $image = imagecreatefrompng($file);
  393. break;
  394. default:
  395. break;
  396. }
  397. if ($image == null) {
  398. return [];
  399. } else {
  400. return [imagesx($image), imagesy($image)];
  401. }
  402. }
  403. /**
  404. * 文件和图片管理action使用
  405. *
  406. * @param $allowFiles
  407. * @param $listSize
  408. * @param $path
  409. * @return array
  410. */
  411. protected function manage($allowFiles, $listSize, $path)
  412. {
  413. $allowFiles = substr(str_replace('.', '|', join('', $allowFiles)), 1);
  414. /* 获取参数 */
  415. $size = isset($_GET['size']) ? $_GET['size'] : $listSize;
  416. $start = isset($_GET['start']) ? $_GET['start'] : 0;
  417. $end = $start + $size;
  418. /* 获取文件列表 */
  419. $path = Yii::getAlias('@ueditor') . (substr($path, 0, 1) == '/' ? '' : '/') . $path;
  420. $files = $this->getFiles($path, $allowFiles);
  421. $len = 0;
  422. if( $files !== null ){
  423. $len = count($files);
  424. }
  425. if ( $len == 0 ) {
  426. $result = [
  427. 'state' => 'no match file',
  428. 'list' => [],
  429. 'start' => $start,
  430. 'total' => $len,
  431. ];
  432. return $result;
  433. }
  434. /* 获取指定范围的列表 */
  435. $len = count($files);
  436. for ($i = min($end, $len) - 1, $list = []; $i < $len && $i >= 0 && $i >= $start; $i--) {
  437. $list[] = $files[$i];
  438. }
  439. /* 返回数据 */
  440. $result = [
  441. 'state' => 'SUCCESS',
  442. 'list' => $list,
  443. 'start' => $start,
  444. 'total' => $len,
  445. ];
  446. return $result;
  447. }
  448. /**
  449. * 遍历获取目录下的指定类型的文件
  450. *
  451. * @param $path
  452. * @param $allowFiles
  453. * @param array $files
  454. * @return array|null
  455. */
  456. protected function getFiles($path, $allowFiles, &$files = [])
  457. {
  458. if (! is_dir($path)) {
  459. return null;
  460. }
  461. if (in_array(basename($path), $this->ignoreDir)) {
  462. return null;
  463. }
  464. if (substr($path, strlen($path) - 1) != '/') {
  465. $path .= '/';
  466. }
  467. $handle = opendir($path);
  468. //baseUrl用于兼容使用alias的二级目录部署方式
  469. $baseUrl = str_replace(Yii::getAlias('@frontend/web'), '', Yii::getAlias('@ueditor'));
  470. while (false !== ($file = readdir($handle))) {
  471. if ($file != '.' && $file != '..') {
  472. $path2 = $path . $file;
  473. if (is_dir($path2)) {
  474. $this->getFiles($path2, $allowFiles, $files);
  475. } else {
  476. if ($this->controller->action->id == 'list-image' && $this->thumbnail) {
  477. $pat = "/\.thumbnail\.(" . $allowFiles . ")$/i";
  478. } else {
  479. $pat = "/\.(" . $allowFiles . ")$/i";
  480. }
  481. if (preg_match($pat, $file)) {
  482. $files[] = [
  483. 'url' => Yii::$app->params['site']['url'] . $baseUrl . substr($path2, strlen(Yii::getAlias('@ueditor'))),
  484. 'mtime' => filemtime($path2)
  485. ];
  486. }
  487. }
  488. }
  489. }
  490. return $files;
  491. }
  492. /**
  493. * 最终显示结果,自动输出 JSONP 或者 JSON
  494. *
  495. * @param array $result
  496. * @return array
  497. */
  498. protected function show($result)
  499. {
  500. $callback = Yii::$app->request->get('callback', null);
  501. if ($callback && is_string($callback)) {
  502. Yii::$app->response->format = Response::FORMAT_JSONP;
  503. return [
  504. 'callback' => $callback,
  505. 'data' => $result
  506. ];
  507. }
  508. Yii::$app->response->format = Response::FORMAT_JSON;
  509. return $result;
  510. }
  511. }