Comment.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. <?php
  2. namespace App\Models;
  3. use Illuminate\Database\Eloquent\Model;
  4. use Illuminate\Support\Facades\DB;
  5. use Kalnoy\Nestedset\NodeTrait;
  6. use App\Http\Traits\PagingTrait;
  7. use App\Models\DTO\SearchData;
  8. class Comment extends Model
  9. {
  10. use NodeTrait;
  11. use PagingTrait;
  12. protected $table = 'tb_comment';
  13. protected $primaryKey = 'id';
  14. public $keyType = 'int';
  15. public $incrementing = true;
  16. public $timestamps = true;
  17. const CREATED_AT = 'created_at';
  18. const UPDATED_AT = 'updated_at';
  19. const DELETED_AT = 'deleted_at';
  20. protected $guarded = [];
  21. public function board()
  22. {
  23. return $this->belongsTo(Board::class)->withDefault();
  24. }
  25. public function post()
  26. {
  27. return $this->belongsTo(Post::class)->withDefault();
  28. }
  29. public function user()
  30. {
  31. return $this->belongsTo(User::class)->withDefault();
  32. }
  33. public function commentblame()
  34. {
  35. return $this->hasMany(CommentBlame::Class);
  36. }
  37. public function commentDeleted()
  38. {
  39. return $this->hasMany(CommentDeleted::Class);
  40. }
  41. public function commentHistory()
  42. {
  43. return $this->hasMany(CommentHistory::Class);
  44. }
  45. public function commentLike()
  46. {
  47. return $this->hasMany(CommentLike::Class);
  48. }
  49. public function commentMeta()
  50. {
  51. return $this->hasOne(CommentMeta::Class);
  52. }
  53. public function getParentIdName(): string
  54. {
  55. return 'parent_id';
  56. }
  57. public function getLftName(): string
  58. {
  59. return 'lft';
  60. }
  61. public function getRgtName(): string
  62. {
  63. return 'rgt';
  64. }
  65. /**
  66. * 댓글 조회
  67. */
  68. public function data(SearchData $params): object
  69. {
  70. $query = $this->query();
  71. $query->select(
  72. 'tb_comment.*',
  73. 'tb_board.name AS boardName',
  74. 'tb_board.code',
  75. 'tb_post.subject',
  76. 'users.id',
  77. 'users.sid',
  78. 'users.name',
  79. 'users.email'
  80. );
  81. if($params->keyword) {
  82. switch ($params->field) {
  83. case 'tb_comment.id' :
  84. case 'tb_comment.post_id' :
  85. case 'users.id' :
  86. case 'users.sid' :
  87. $query->where($params->field, '=', $params->keyword);
  88. break;
  89. case 'tb_comment.content' :
  90. case 'users.name' :
  91. case 'users.email' :
  92. $query->where($params->field, 'LIKE', "%{$params->keyword}%");
  93. break;
  94. }
  95. }
  96. $query->where('tb_comment.is_delete', '=', 0);
  97. if($params->startDate || $params->endDate) {
  98. if($params->startDate) {
  99. $query->where('tb_comment.created_at', '>=', $params->startDate . ' 00:00:00');
  100. }
  101. if($params->endDate) {
  102. $query->where('tb_comment.created_at', '<=', $params->endDate . ' 23:59:59');
  103. }
  104. }
  105. if($params->boardID) {
  106. $query->where('tb_comment.board_id', '=', $params->boardID);
  107. }
  108. $query->join('tb_board', 'tb_board.id', '=', 'tb_comment.board_id');
  109. $query->join('tb_post', 'tb_post.id', '=', 'tb_comment.post_id');
  110. $query->leftJoin('users', 'users.id', '=', 'tb_comment.user_id');
  111. $query->orderByDesc('tb_comment.id');
  112. $list = $query->paginate($params->perPage, ['*'], 'page', $params->page);
  113. $total = $this->count();
  114. $rows = $list->count();
  115. return (object)[
  116. 'total' => $total,
  117. 'rows' => $rows,
  118. 'list' => $list
  119. ];
  120. }
  121. /**
  122. * 댓글 전체 개수
  123. */
  124. public function total(int $postID): int
  125. {
  126. return $this->where('post_id', $postID)->count();
  127. }
  128. /**
  129. * 댓글 등록
  130. */
  131. public function register(array $params): Comment
  132. {
  133. return $this->create($params);
  134. }
  135. /**
  136. * 댓글 수정
  137. */
  138. public function updater(int $commentID, array $params): Comment
  139. {
  140. return $this->updateOrCreate([$this->primaryKey => $commentID], $params);
  141. }
  142. /**
  143. * 답글 등록
  144. */
  145. public function reply(int $commentID, array $params): Comment
  146. {
  147. $parent = $this->findParent($commentID);
  148. $node = $parent->children()->create($params);
  149. $node->depth = ($parent->depth + 1);
  150. $node->save();
  151. return $node;
  152. }
  153. /**
  154. * 댓글 조회
  155. */
  156. public function get(int $commentID): Comment
  157. {
  158. return $this->from('tb_comment AS CMT')->select('CMT.*')
  159. ->with(['board', 'post', 'user'])->where([
  160. ['CMT.id', $commentID],
  161. ['CMT.is_delete', 0]
  162. ])->firstOrNew();
  163. }
  164. /**
  165. * 댓글 조회
  166. */
  167. public function getList(string $code, int $postID, int $page, int $offset, int $perPage, int $sort): object
  168. {
  169. $orderBy = $this->sortQuery($sort);
  170. $sql = "
  171. SELECT
  172. COUNT(CMT.id) AS total
  173. FROM tb_comment CMT
  174. JOIN tb_post PST ON PST.id = CMT.post_id
  175. JOIN tb_board BRD ON BRD.id = PST.board_id
  176. WHERE TRUE
  177. AND BRD.code = ?
  178. AND PST.id = ?
  179. AND CASE WHEN (CMT.is_delete = 1 AND CMT.reply <= 0 AND CMT.depth <= 0) THEN FALSE ELSE TRUE END
  180. ";
  181. $total = DB::selectOne($sql, [$code, $postID])->total;
  182. $sql = "
  183. SELECT
  184. @replyRows := (SELECT (COUNT(id) - 1) FROM tb_comment WHERE lft BETWEEN CMT.lft AND CMT.rgt) AS replyRows,
  185. @depth := (SELECT (COUNT(*) - 1) FROM tb_comment WHERE CMT.rgt BETWEEN lft AND rgt),
  186. CMT.*,
  187. USR.name,
  188. USR.thumb,
  189. USR.icon,
  190. IFNULL(CLIK.`type`, 0) AS likeType
  191. FROM tb_comment CMT
  192. JOIN tb_comment PCMT ON CMT.lft BETWEEN PCMT.lft AND PCMT.rgt
  193. JOIN tb_post PST ON PST.id = CMT.post_id
  194. JOIN tb_board BRD ON BRD.id = PST.board_id
  195. LEFT JOIN tb_comment_like CLIK ON CLIK.post_id = CMT.post_id AND CMT.id = CLIK.comment_id
  196. LEFT JOIN users USR ON USR.id = CMT.user_id
  197. WHERE TRUE
  198. AND BRD.code = ?
  199. AND PST.id = ?
  200. AND CASE WHEN (CMT.is_delete = 1 AND @replyRows <= 0 AND @depth <= 0) THEN FALSE ELSE TRUE END
  201. GROUP BY
  202. CMT.id,
  203. CLIK.`type`
  204. ORDER BY
  205. {$orderBy}
  206. LIMIT ?, ?;
  207. ";
  208. $list = $this->getPaginator(
  209. DB::select($sql, [$code, $postID, $offset, $perPage]), $total, $perPage, $page
  210. );
  211. return (object)[
  212. 'total' => $total,
  213. 'list' => $list
  214. ];
  215. }
  216. /**
  217. * 회원 댓글 목록 조회
  218. */
  219. public function getUserList(int $userID, SearchData $params): object
  220. {
  221. $sql = "
  222. SELECT
  223. COUNT(CMT.id) AS total
  224. FROM tb_comment CMT
  225. JOIN tb_post PST ON PST.id = CMT.post_id
  226. JOIN tb_board BRD ON BRD.id = PST.board_id
  227. LEFT JOIN users USR ON USR.id = CMT.user_id
  228. WHERE TRUE
  229. AND USR.id = ?
  230. AND CMT.is_delete = 0;
  231. ";
  232. $total = DB::selectOne($sql, [$userID])->total;
  233. $sql = "
  234. SELECT
  235. @replyRows := (SELECT (COUNT(id) - 1) FROM tb_comment WHERE lft BETWEEN CMT.lft AND CMT.rgt) AS replyRows,
  236. @depth := (SELECT (COUNT(*) - 1) FROM tb_comment WHERE CMT.rgt BETWEEN lft AND rgt),
  237. CMT.*,
  238. BRD.code,
  239. PST.subject, PST.comment_rows, PST.file_rows, PST.image_rows, PST.link_rows,
  240. USR.name, USR.thumb, USR.icon
  241. FROM tb_comment CMT
  242. JOIN tb_comment PCMT ON CMT.lft BETWEEN PCMT.lft AND PCMT.rgt
  243. JOIN tb_post PST ON PST.id = CMT.post_id
  244. JOIN tb_board BRD ON BRD.id = CMT.board_id
  245. LEFT JOIN users USR ON USR.id = CMT.user_id
  246. WHERE TRUE
  247. AND USR.id = ?
  248. AND CMT.is_delete = 0
  249. GROUP BY
  250. CMT.id
  251. ORDER BY
  252. CMT.lft ASC, CMT.created_at ASC
  253. LIMIT ?, ?;
  254. ";
  255. $list = $this->getPaginator(
  256. DB::select($sql, [$userID, $params->offset, $params->perPage]), $total, $params->perPage, $params->page
  257. );
  258. return (object)[
  259. 'total' => $total,
  260. 'list' => $list
  261. ];
  262. }
  263. /**
  264. * 정렬 순서
  265. */
  266. private function sortQuery(int $sort): string
  267. {
  268. switch($sort) {
  269. default:case 1: // 등록순
  270. return 'CMT.lft ASC, CMT.created_at ASC';
  271. case 2: // 최신순
  272. return 'CMT.rgt DESC';
  273. case 3: // 공감 순
  274. return '
  275. CASE WHEN (CMT.parent_id IS NULL AND CMT.reply <= 0) THEN CMT.like
  276. WHEN TRUE THEN TRUE
  277. ELSE NULL
  278. END DESC
  279. , CMT.lft ASC
  280. ';
  281. case 4: // 댓글 순
  282. return '
  283. CASE WHEN (CMT.parent_id IS NULL AND CMT.reply <= 0) THEN CMT.like
  284. WHEN TRUE THEN TRUE
  285. ELSE NULL
  286. END DESC
  287. , CMT.lft ASC
  288. ';
  289. }
  290. }
  291. /**
  292. * 댓글 조회
  293. */
  294. public function findParent(int $commentID)
  295. {
  296. return $this->newQuery()->withDepth()->find($commentID);
  297. }
  298. /**
  299. * 댓글 신고 수 증가
  300. */
  301. public function increaseBlame(int $commentID): int
  302. {
  303. return $this->where('id', $commentID)->increment('blame');
  304. }
  305. /**
  306. * 댓글 신고 수 증감
  307. */
  308. public function decreaseBlame(int $commentID): int
  309. {
  310. return $this->where('id', $commentID)->where('blame', '>', '0')->decrement('blame', 1);
  311. }
  312. /**
  313. * 댓글 좋아요 수 증가
  314. */
  315. public function increaseLike(int $commentID): int
  316. {
  317. return $this->where('id', $commentID)->increment('like');
  318. }
  319. /**
  320. * 댓글 좋아요 수 증감
  321. */
  322. public function decreaseLike(int $commentID): int
  323. {
  324. return $this->where('id', $commentID)->where('like', '>', '0')->decrement('like', 1);
  325. }
  326. /**
  327. * 댓글 싫어요 수 증가
  328. */
  329. public function increaseDisLike(int $commentID): int
  330. {
  331. return $this->where('id', $commentID)->increment('dislike');
  332. }
  333. /**
  334. * 댓글 싫어요 수 증감
  335. */
  336. public function decreaseDisLike(int $commentID): int
  337. {
  338. return $this->where('id', $commentID)->where('dislike', '>', '0')->decrement('dislike');
  339. }
  340. /**
  341. * 댓글 수 증가
  342. */
  343. public function increaseReply(int $commentID): int
  344. {
  345. return $this->where('id', $commentID)->increment('reply');
  346. }
  347. /**
  348. * 댓글 수 증감
  349. */
  350. public function decreaseReply(int $commentID): int
  351. {
  352. return $this->where('id', $commentID)->where('reply', '>', '0')->decrement('reply');
  353. }
  354. /**
  355. * 댓글 개수 갱신
  356. */
  357. public function updateCommentRows(int $commentID): int
  358. {
  359. $sql = "
  360. UPDATE tb_comment A, (
  361. SELECT
  362. (COUNT(*) - 1) AS total
  363. FROM tb_comment CMT
  364. JOIN tb_comment PCMT ON CMT.lft BETWEEN PCMT.lft AND PCMT.rgt
  365. WHERE PCMT.id = :id
  366. ) B
  367. SET A.reply = B.total
  368. WHERE A.id = :id;
  369. ";
  370. return DB::update($sql, [
  371. 'id' => $commentID
  372. ]);
  373. }
  374. /**
  375. * 댓글의 현재 페이지 번호
  376. */
  377. public function findPageNumber(int $commentID, int $perPage = DEFAULT_LIST_PER_PAGE): int
  378. {
  379. $sql = "
  380. SELECT
  381. D.cnt,
  382. COALESCE(CEIL((D.cnt - 1) / :perPage), 1) AS page
  383. FROM (
  384. SELECT
  385. *, @num := (@num + 1) AS cnt
  386. FROM
  387. tb_comment,
  388. (SELECT @num := 0) AS N1,
  389. (SELECT
  390. @boardID := board_id, @postID := post_id, @commentID := id
  391. FROM tb_comment WHERE id = :commentID
  392. ) AS N2
  393. WHERE TRUE
  394. AND board_id = @boardID
  395. AND post_id = @postID
  396. ORDER BY lft
  397. ) D
  398. WHERE D.id = @commentID;
  399. ";
  400. return DB::selectOne($sql, [
  401. 'commentID' => $commentID,
  402. 'perPage' => $perPage
  403. ])->page;
  404. }
  405. /**
  406. * 댓글 존재 확인
  407. */
  408. public function isExists(int $commentID): int
  409. {
  410. $sql = "
  411. SELECT IF(COUNT(*) > 0, 1, 0) AS isExists
  412. FROM tb_comment CMT JOIN tb_post PST ON PST.id = CMT.post_id
  413. WHERE CMT.id = ? AND CMT.is_delete = 0;
  414. ";
  415. return DB::selectOne($sql, [$commentID])->isExists;
  416. }
  417. /**
  418. * 내가 작성한 댓글인지 확인
  419. */
  420. public function getChildRows(int $commentID): int
  421. {
  422. $sql = "
  423. SELECT (COUNT(CMT.comment_id) - 1) AS `rows` FROM tb_comment CMT
  424. JOIN tb_comment PCMT ON CMT.lft BETWEEN PCMT.lft AND PCMT.rgt
  425. WHERE PCMT.id = ?
  426. ";
  427. return DB::selectOne($sql, [$commentID])->rows;
  428. }
  429. /**
  430. * 추가 댓글 번호
  431. */
  432. public function lastedKey(): int
  433. {
  434. return intval(DB::selectOne(
  435. "SELECT AUTO_INCREMENT AS id FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'tb_comment';"
  436. )->id);
  437. }
  438. /**
  439. * 댓글 삭제
  440. */
  441. public function remove(Comment $comment, ?User $user): mixed
  442. {
  443. return DB::transaction(function() use ($comment, $user)
  444. {
  445. // 댓글 삭제 여부 승인
  446. $this->updater($comment->id, [
  447. 'is_delete' => 1,
  448. 'deleted_at' => now()
  449. ]);
  450. // 댓글 삭제 정보 추가
  451. (new CommentDeleted)->register([
  452. 'board_id' => $comment->board_id,
  453. 'post_id' => $comment->post_id,
  454. 'comment_id' => $comment->id,
  455. 'user_id' => $user?->id,
  456. 'ip_address' => IP_ADDRESS,
  457. 'user_agent' => USER_AGENT,
  458. 'created_at' => now()
  459. ]);
  460. // 게시판 댓글 수 갱신
  461. (new Board)->updateCommentRows($comment->board_id);
  462. // 게시판 댓글 수 갱신
  463. (new Post)->updateCommentRows($comment->post_id);
  464. // 부모 댓글 수 갱신
  465. $this->updateCommentRows($comment->id);
  466. return true;
  467. });
  468. }
  469. }