PortalPostModel.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkCMF [ WE CAN DO IT MORE SIMPLE ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2013-2017 http://www.thinkcmf.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Author: pl125 <xskjs888@163.com>
  8. // +----------------------------------------------------------------------
  9. namespace api\portal\model;
  10. use think\Db;
  11. use think\db\Query;
  12. use think\Model;
  13. /**
  14. * @method getFieldById($id, $string)
  15. * @property mixed id
  16. */
  17. class PortalPostModel extends Model
  18. {
  19. /**
  20. * 模型名称
  21. * @var string
  22. */
  23. protected $name = 'portal_post';
  24. //设置只读字段
  25. protected $readonly = ['user_id'];
  26. // 开启自动写入时间戳字段
  27. protected $autoWriteTimestamp = true;
  28. //类型转换
  29. protected $type = [
  30. 'more' => 'array',
  31. ];
  32. /**
  33. * 关联 user表
  34. * @return \think\model\relation\BelongsTo
  35. */
  36. public function user()
  37. {
  38. return $this->belongsTo('api\portal\model\UserModel', 'user_id');
  39. }
  40. /**
  41. * 关联 user表
  42. * @return \think\model\relation\BelongsTo
  43. */
  44. public function articleUser()
  45. {
  46. return $this->belongsTo('api\portal\model\UserModel', 'user_id')->field('id,user_nickname');
  47. }
  48. /**
  49. * 关联分类表
  50. * @return \think\model\relation\BelongsToMany
  51. */
  52. public function categories()
  53. {
  54. return $this->belongsToMany('api\portal\model\PortalCategoryModel', 'portal_category_post', 'category_id', 'post_id');
  55. }
  56. /**
  57. * 关联标签表
  58. * @return \think\model\relation\BelongsToMany
  59. */
  60. public function tags()
  61. {
  62. return $this->belongsToMany('api\portal\model\PortalTagModel', 'portal_tag_post', 'tag_id', 'post_id');
  63. }
  64. /**
  65. * 关联 回收站 表
  66. * @return \think\model\relation\HasOne
  67. */
  68. public function recycleBin()
  69. {
  70. return $this->hasOne('api\portal\model\RecycleBinModel', 'object_id');
  71. }
  72. /**
  73. * published_time 自动转化
  74. * @param $value
  75. * @return string
  76. */
  77. public function getPublishedTimeAttr($value)
  78. {
  79. // 兼容老版本 1.0.0的客户端
  80. $apiVersion = request()->header('XX-Api-Version');
  81. if (empty($apiVersion)) {
  82. return date('Y-m-d H:i:s', $value);
  83. } else {
  84. return $value;
  85. }
  86. }
  87. /**
  88. * published_time 自动转化
  89. * @param $value
  90. * @return int
  91. */
  92. public function setPublishedTimeAttr($value)
  93. {
  94. if (is_numeric($value)) {
  95. return $value;
  96. }
  97. return strtotime($value);
  98. }
  99. public function getPostTitleAttr($value)
  100. {
  101. return htmlspecialchars_decode($value);
  102. }
  103. public function getPostExcerptAttr($value)
  104. {
  105. return htmlspecialchars_decode($value);
  106. }
  107. /**
  108. * post_content 自动转化
  109. * @param $value
  110. * @return string
  111. */
  112. public function getPostContentAttr($value)
  113. {
  114. return cmf_replace_content_file_url(htmlspecialchars_decode($value));
  115. }
  116. /**
  117. * post_content 自动转化
  118. * @param $value
  119. * @return string
  120. */
  121. public function setPostContentAttr($value)
  122. {
  123. return htmlspecialchars(cmf_replace_content_file_url(htmlspecialchars_decode($value), true));
  124. }
  125. /**
  126. * Thumbnail 自动转化
  127. * @param $value
  128. * @return array
  129. */
  130. public function getThumbnailAttr($value)
  131. {
  132. return cmf_get_image_url($value);
  133. }
  134. /**
  135. * more 自动转化
  136. * @param $value
  137. * @return array
  138. */
  139. public function getMoreAttr($value)
  140. {
  141. $more = json_decode($value, true);
  142. if (!empty($more['thumbnail'])) {
  143. $more['thumbnail'] = cmf_get_image_url($more['thumbnail']);
  144. }
  145. if (!empty($more['audio'])) {
  146. $more['audio'] = cmf_get_file_download_url($more['audio']);
  147. }
  148. if (!empty($more['video'])) {
  149. $more['video'] = cmf_get_file_download_url($more['video']);
  150. }
  151. if (!empty($more['photos'])) {
  152. foreach ($more['photos'] as $key => $value) {
  153. $more['photos'][$key]['url'] = cmf_get_image_url($value['url']);
  154. }
  155. }
  156. if (!empty($more['files'])) {
  157. foreach ($more['files'] as $key => $value) {
  158. $more['files'][$key]['url'] = cmf_get_file_download_url($value['url']);
  159. }
  160. }
  161. return $more;
  162. }
  163. /**
  164. * 文章查询
  165. * @param array $filter 数据
  166. * @return array|\PDOStatement|string|Model|null
  167. * @throws \think\db\exception\DataNotFoundException
  168. * @throws \think\db\exception\ModelNotFoundException
  169. * @throws \think\exception\DbException
  170. */
  171. public function articleFind($filter)
  172. {
  173. $result = $this
  174. ->where(function (Query $query) use ($filter) {
  175. if (!empty($filter['id'])) {
  176. $query->where('id', $filter['id']);
  177. }
  178. if (!empty($filter['user_id'])) {
  179. $query->where('user_id', $filter['user_id']);
  180. }
  181. })
  182. ->where('delete_time', 0)
  183. ->where('post_status', 1)
  184. ->where('post_type', 1)
  185. ->find();
  186. return $result;
  187. }
  188. /**
  189. * 会员添加文章
  190. * @param array $data 文章数据
  191. * @return $this
  192. * @throws \think\Exception
  193. */
  194. public function addArticle($data)
  195. {
  196. if (!empty($data['more'])) {
  197. $data['more'] = $this->setMoreUrl($data['more']);
  198. }
  199. if (!empty($data['thumbnail'])) {
  200. $data['more']['thumbnail'] = cmf_asset_relative_url($data['thumbnail']);
  201. }
  202. $this->allowField(true)->data($data, true)->isUpdate(false)->save();
  203. $categories = str_to_arr($data['categories']);
  204. //TODO 无法录入多个分类
  205. $this->categories()->attach($categories);
  206. if (!empty($data['post_keywords']) && is_string($data['post_keywords'])) {
  207. //加入标签
  208. $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
  209. $keywords = explode(',', $data['post_keywords']);
  210. $this->addTags($keywords, $this->id);
  211. }
  212. return $this;
  213. }
  214. /**
  215. * 会员文章编辑
  216. * @param array $data 文章数据
  217. * @param int $id 文章id
  218. * @param string $userId 文章所属用户id [可选]
  219. * @return PortalPostModel|bool
  220. * @throws \think\Exception
  221. */
  222. public function editArticle($data, $id, $userId = '')
  223. {
  224. if (!empty($userId)) {
  225. //判断是否属于当前用户的文章
  226. $isBelong = $this->isuserPost($id, $userId);
  227. if ($isBelong === false) {
  228. return $isBelong;
  229. }
  230. }
  231. if (!empty($data['more'])) {
  232. $data['more'] = $this->setMoreUrl($data['more']);
  233. }
  234. if (!empty($data['thumbnail'])) {
  235. $data['more']['thumbnail'] = cmf_asset_relative_url($data['thumbnail']);
  236. }
  237. $data['id'] = $id;
  238. // $data['post_status'] = empty($data['post_status']) ? 0 : 1;
  239. // $data['is_top'] = empty($data['is_top']) ? 0 : 1;
  240. // $data['recommended'] = empty($data['recommended']) ? 0 : 1;
  241. $this->allowField(true)->data($data, true)->isUpdate(true)->save();
  242. $categories = str_to_arr($data['categories']);
  243. $oldCategoryIds = $this->categories()->column('category_id');
  244. $sameCategoryIds = array_intersect($categories, $oldCategoryIds);
  245. $needDeleteCategoryIds = array_diff($oldCategoryIds, $sameCategoryIds);
  246. $newCategoryIds = array_diff($categories, $sameCategoryIds);
  247. if (!empty($needDeleteCategoryIds)) {
  248. $this->categories()->detach($needDeleteCategoryIds);
  249. }
  250. if (!empty($newCategoryIds)) {
  251. $this->categories()->attach(array_values($newCategoryIds));
  252. }
  253. $keywords = [];
  254. if (!empty($data['post_keywords'])) {
  255. if (is_string($data['post_keywords'])) {
  256. //加入标签
  257. $data['post_keywords'] = str_replace(',', ',', $data['post_keywords']);
  258. $keywords = explode(',', $data['post_keywords']);
  259. }
  260. }
  261. $this->addTags($keywords, $data['id']);
  262. return $this;
  263. }
  264. /**
  265. * 根据文章关键字,增加标签
  266. * @param array $keywords 文章关键字数组
  267. * @param int $articleId 文章id
  268. * @throws \think\Exception
  269. * @throws \think\db\exception\DataNotFoundException
  270. * @throws \think\db\exception\ModelNotFoundException
  271. * @throws \think\exception\DbException
  272. * @throws \think\exception\PDOException
  273. */
  274. public function addTags($keywords, $articleId)
  275. {
  276. foreach ($keywords as $key => $value) {
  277. $keywords[$key] = trim($value);
  278. }
  279. $continue = true;
  280. $names = $this->tags()->column('name');
  281. if (!empty($keywords) || !empty($names)) {
  282. if (!empty($names)) {
  283. $sameNames = array_intersect($keywords, $names);
  284. $keywords = array_diff($keywords, $sameNames);
  285. $shouldDeleteNames = array_diff($names, $sameNames);
  286. if (!empty($shouldDeleteNames)) {
  287. $tagIdNames = $this->tags()
  288. ->where('name', 'in', $shouldDeleteNames)
  289. ->column('pivot.id', 'tag_id');
  290. $tagIds = array_keys($tagIdNames);
  291. $tagPostIds = array_values($tagIdNames);
  292. $tagPosts = DB::name('portal_tag_post')
  293. ->where('tag_id', 'in', $tagIds)
  294. ->field('id,tag_id,post_id')
  295. ->select();
  296. $keepTagIds = [];
  297. foreach ($tagPosts as $key => $tagPost) {
  298. if ($articleId != $tagPost['post_id']) {
  299. array_push($keepTagIds, $tagPost['tag_id']);
  300. }
  301. }
  302. $keepTagIds = array_unique($keepTagIds);
  303. $shouldDeleteTagIds = array_diff($tagIds, $keepTagIds);
  304. Db::name('PortalTag')->delete($shouldDeleteTagIds);
  305. Db::name('PortalTagPost')->delete($tagPostIds);
  306. }
  307. } else {
  308. $tagIdNames = DB::name('portal_tag')->where('name', 'in', $keywords)->column('name', 'id');
  309. if (!empty($tagIdNames)) {
  310. $tagIds = array_keys($tagIdNames);
  311. $this->tags()->attach($tagIds);
  312. $keywords = array_diff($keywords, array_values($tagIdNames));
  313. if (empty($keywords)) {
  314. $continue = false;
  315. }
  316. }
  317. }
  318. if ($continue) {
  319. foreach ($keywords as $key => $value) {
  320. if (!empty($value)) {
  321. $this->tags()->attach(['name' => $value]);
  322. }
  323. }
  324. }
  325. }
  326. }
  327. /**
  328. * 获取图片附件url相对地址
  329. * 默认上传名字 *_names 地址 *_urls
  330. * @param array $annex 上传附件
  331. * @return array
  332. */
  333. public function setMoreUrl($annex)
  334. {
  335. $more = [];
  336. if (!empty($annex)) {
  337. foreach ($annex as $key => $value) {
  338. $nameArr = $key . '_names';
  339. $urlArr = $key . '_urls';
  340. if (is_string($value[$nameArr]) && is_string($value[$urlArr])) {
  341. $more[$key] = [$value[$nameArr], $value[$urlArr]];
  342. } elseif (!empty($value[$nameArr]) && !empty($value[$urlArr])) {
  343. $more[$key] = [];
  344. foreach ($value[$urlArr] as $k => $url) {
  345. $url = cmf_asset_relative_url($url);
  346. array_push($more[$key], ['url' => $url, 'name' => $value[$nameArr][$k]]);
  347. }
  348. }
  349. }
  350. }
  351. return $more;
  352. }
  353. /**
  354. * 删除文章
  355. * @param int|array $ids 文章id
  356. * @param string $userId 文章所属用户id [可选]
  357. * @return bool|int 删除结果 true 成功 false 失败 -1 文章不存在
  358. * @throws \think\db\exception\DataNotFoundException
  359. * @throws \think\db\exception\ModelNotFoundException
  360. * @throws \think\exception\DbException
  361. */
  362. public function deleteArticle($ids, $userId = '')
  363. {
  364. $time = time();
  365. $result = false;
  366. $where = [];
  367. if (!empty($userId)) {
  368. if (is_numeric($ids)) {
  369. $article = $this->find($ids);
  370. if (!empty($article)) {
  371. if ($this->isUserPost($ids, $userId) || $userId == 1) {
  372. $where['id'] = $ids;
  373. }
  374. }
  375. } else {
  376. $ids = str_to_arr($ids);
  377. $articles = $this->where('id', 'in', $ids)->select();
  378. if (!empty($articles)) {
  379. $deleteIds = $this->isUserPosts($ids, $userId);
  380. if (!empty($deleteIds)) {
  381. $where['id'] = ['in', $deleteIds];
  382. }
  383. }
  384. }
  385. } else {
  386. if (is_numeric($ids)) {
  387. $article = $this->find($ids);
  388. if (!empty($article)) {
  389. $where['id'] = $ids;
  390. }
  391. } else {
  392. $ids = str_to_arr($ids);
  393. $articles = $this->where('id', 'in', $ids)->select();
  394. if (!empty($articles)) {
  395. $where['id'] = ['in', $ids];
  396. }
  397. }
  398. }
  399. if (empty($article) && empty($articles)) {
  400. return -1;
  401. }
  402. if (!empty($where)) {
  403. $result = $this->useGlobalScope(false)
  404. ->where($where)
  405. ->setField('delete_time', $time);
  406. }
  407. if ($result) {
  408. $data = [
  409. 'create_time' => $time,
  410. 'table_name' => 'portal_post'
  411. ];
  412. if (!empty($article)) {
  413. $data['name'] = $article['post_title'];
  414. $article->recycleBin()->save($data);
  415. }
  416. if (!empty($articles)) {
  417. foreach ($articles as $article) {
  418. $data['name'] = $article['post_title'];
  419. $article->recycleBin()->save($data);
  420. }
  421. }
  422. }
  423. return $result;
  424. }
  425. /**
  426. * 判断文章所属用户是否为当前用户,超级管理员除外
  427. * @param int $id 文章id
  428. * @param int $userId 当前用户id
  429. * @return boolean 是 true , 否 false
  430. */
  431. public function isUserPost($id, $userId)
  432. {
  433. $postUserId = $this->getFieldById($id, 'user_id');
  434. if ($postUserId == $userId || $userId == 1) {
  435. return true;
  436. } else {
  437. return false;
  438. }
  439. }
  440. /**
  441. * 过滤属于当前用户的文章,超级管理员除外
  442. * @param array $ids 文章id的数组
  443. * @param int $userId 当前用户id
  444. * @return array 属于当前用户的文章id
  445. */
  446. public function isUserPosts($ids, $userId)
  447. {
  448. $postIds = $this
  449. ->useGlobalScope(false)
  450. ->where('user_id', $userId)
  451. ->where('id', 'in', $ids)
  452. ->column('id');
  453. return array_intersect($ids, $postIds);
  454. }
  455. }