Comment.php 15 KB

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