CommentService.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Http\Request;
  4. use Illuminate\Support\Facades\DB;
  5. use App\Http\Requests\CommentRequest;
  6. use App\Http\Traits\CommonTrait;
  7. use App\Http\Traits\BoardTrait;
  8. use App\Models\User;
  9. use App\Models\Board;
  10. use App\Models\BoardMeta;
  11. use App\Models\Post;
  12. use App\Models\Comment;
  13. use App\Models\CommentMeta;
  14. use App\Models\CommentHistory;
  15. use App\Models\CommentLike;
  16. use App\Models\CommentBlame;
  17. use App\Models\CommentDeleted;
  18. use App\Models\FileLib;
  19. use App\Models\EmailLib;
  20. use App\Models\EditorImage;
  21. use App\Models\Exp;
  22. use App\Models\DTO\ResponseData;
  23. use App\Models\DTO\SearchData;
  24. use Exception;
  25. class CommentService
  26. {
  27. use CommonTrait;
  28. use BoardTrait;
  29. public Board $boardModel;
  30. public BoardMeta $boardMetaModel;
  31. public Post $postModel;
  32. public Comment $commentModel;
  33. public CommentMeta $commentMetaModel;
  34. public CommentHistory $commentHistoryModel;
  35. public CommentLike $commentLikeModel;
  36. public CommentBlame $commentBlameModel;
  37. public CommentDeleted $commentDeletedModel;
  38. public User $userModel;
  39. public FileLib $fileLib;
  40. public EmailLib $emailLib;
  41. public EditorImage $editorImageModel;
  42. public function __construct()
  43. {
  44. $this->boardModel = new Board();
  45. $this->boardMetaModel = new BoardMeta();
  46. $this->postModel = new Post();
  47. $this->commentModel = new Comment();
  48. $this->commentMetaModel = new CommentMeta();
  49. $this->commentHistoryModel = new CommentHistory();
  50. $this->commentLikeModel = new CommentLike();
  51. $this->commentBlameModel = new CommentBlame();
  52. $this->commentDeletedModel = new CommentDeleted();
  53. $this->userModel = new User();
  54. $this->fileLib = new FileLib();
  55. $this->emailLib = new EmailLib();
  56. $this->editorImageModel = new EditorImage();
  57. }
  58. public function meta(int $commentID): ?CommentMeta
  59. {
  60. return $this->commentMetaModel->getAllMeta($commentID);
  61. }
  62. public function find(int $commentID): Comment
  63. {
  64. return $this->_filter($this->commentModel->get($commentID));
  65. }
  66. /**
  67. * 댓글 전체 수
  68. */
  69. public function total(int $postID): int
  70. {
  71. return $this->commentModel->total($postID);
  72. }
  73. /**
  74. * 전체 댓글 목록 조회
  75. */
  76. public function list(string $code, int $postID, SearchData $params): object
  77. {
  78. $comments = $this->commentModel->getList($code, $postID, $params->page, $params->offset, $params->perPage, $params->sort);
  79. if($comments->total > 0) {
  80. $num = listNum($comments->total, $params->page, $params->perPage);
  81. foreach($comments->list as $i => $row) {
  82. $row = $this->commentModel->newInstance(get_object_vars($row), true);
  83. $row->num = $num--;
  84. $comments->list[$i] = $this->_filter($row);
  85. }
  86. }
  87. return $comments;
  88. }
  89. /**
  90. * 회원 댓글 조회
  91. */
  92. public function userList(int $userID, SearchData $params): object
  93. {
  94. $comments = $this->commentModel->getUserList($userID, $params);
  95. if($comments->total > 0) {
  96. $num = listNum($comments->total, $params->page, $params->perPage);
  97. foreach($comments->list as $i => $row) {
  98. $row = $this->commentModel->newInstance(get_object_vars($row), true);
  99. $row->num = $num--;
  100. $row->page = $this->commentModel->findPageNumber($row->id, $params->perPage);
  101. $comments->list[$i] = $this->_filter($row);
  102. }
  103. }
  104. return $comments;
  105. }
  106. /**
  107. * 댓글 정보 가공
  108. */
  109. private function _filter(Comment $comment): Comment
  110. {
  111. if(!$comment->exists) {
  112. return $comment;
  113. }
  114. $boardMeta = $this->boardMetaModel->getAllMeta($comment->board_id);
  115. $comment->user->thumb = $this->profileThumbSrc($comment->user->thumb);
  116. $comment->viewURL = route('board.post.view', [
  117. $comment->board->code, $comment->post_id
  118. ]);
  119. // 익명 게시판일 경우
  120. if(
  121. $boardMeta->item('use_anonymous', 0)
  122. && (!$comment->user->is_admin || !$boardMeta->item('anonymous_except_admin', 0))
  123. && $comment->user->id
  124. ) {
  125. $comment->username = $this->createAnonymousName(
  126. $boardMeta->item('anonymous_name'), $comment->user->id, $comment->post_id
  127. );
  128. }
  129. // 댓글 설정 정보
  130. $commentMeta = $this->meta($comment->id);
  131. // 관리자에 의한 삭제
  132. $comment->isBlind = $commentMeta->item('is_blind', 0);
  133. // IP 표시 형식
  134. $showCommentIP = $boardMeta->item('show_comment_ip'); // 노출 방법
  135. if ($showCommentIP) {
  136. $ipDisplayStyle = ($showCommentIP == 2 || IS_ADMIN ? '1111' : config('ip_display_style')); // IP 형식
  137. $comment->ipAddress = $this->ipAddrMasking($comment->ip_address, $ipDisplayStyle);
  138. }
  139. // 신고 누적으로 숨김
  140. $comment->isBlame = false;
  141. if($commentBlameBlindCount = $boardMeta->item('comment_blame_blind_count', 0)) {
  142. $comment->isBlame = ($comment->blame >= $commentBlameBlindCount);
  143. }
  144. // 좋아요
  145. $comment->isLike = $this->commentLikeModel->isAlready($comment, UID, LIKE);
  146. // 싫어요
  147. $comment->isDislike = $this->commentLikeModel->isAlready($comment,UID, DISLIKE);
  148. // 작성일시
  149. $comment->createdAt = $this->dateFormat($comment->created_at);
  150. return $comment;
  151. }
  152. /**
  153. * 댓글 등록
  154. */
  155. public function register(CommentRequest $request, ResponseData $response): ResponseData
  156. {
  157. DB::beginTransaction();
  158. try {
  159. $user = $request->user();
  160. $boardMeta = $request->boardMeta;
  161. $boardID = intval($request->post('bid'));
  162. $postID = intval($request->post('pid'));
  163. $content = $this->getContent($request);
  164. // 댓글 정보 저장
  165. $comment = $this->commentModel->register([
  166. 'board_id' => $boardID,
  167. 'post_id' => $postID,
  168. 'user_id' => $user?->id,
  169. 'parent_id' => null,
  170. 'is_reply' => 0,
  171. 'content' => $content,
  172. 'sid' => $user?->sid,
  173. 'username' => $request->post('username', $user?->name),
  174. 'email' => $user?->email,
  175. 'password' => $request->post('password'),
  176. 'reply' => 0,
  177. 'like' => 0,
  178. 'dislike' => 0,
  179. 'blame' => 0,
  180. 'device_type' => DEVICE_TYPE,
  181. 'is_secret' => ($boardMeta->item('use_comment_secret', 0) == '2' ? 1 : $request->post('is_secret', 0)),
  182. 'is_delete' => 0,
  183. 'ip_address' => IP_ADDRESS,
  184. 'user_agent' => USER_AGENT,
  185. 'deleted_at' => null,
  186. 'updated_at' => null,
  187. 'created_at' => now()
  188. ]);
  189. // 게시판 댓글 수 갱신
  190. $this->boardModel->updateCommentRows($boardID);
  191. // 게시글 댓글 수 갱신
  192. $this->postModel->updateCommentRows($postID);
  193. // 댓글 Meta 저장
  194. $this->commentMetaModel->save($comment->id, [
  195. 'is_blind' => 0,
  196. 'exec_blind_key' => null
  197. ]);
  198. // 댓글 기록 저장
  199. if ($boardMeta->item('use_comment_history', 0)) {
  200. $this->commentHistoryModel->register($comment, '댓글 등록');
  201. }
  202. $response->comment = $comment;
  203. DB::commit();
  204. } catch (Exception $e) {
  205. $response = $response::fromException($e);
  206. DB::rollBack();
  207. }
  208. return $response;
  209. }
  210. /**
  211. * 댓글 수정
  212. */
  213. public function updater(CommentRequest $request, ResponseData $response): ResponseData
  214. {
  215. DB::beginTransaction();
  216. try {
  217. $user = $request->user();
  218. $boardMeta = $request->boardMeta;
  219. $boardID = intval($request->post('bid'));
  220. $postID = intval($request->post('pid'));
  221. $commentID = intval($request->post('cid'));
  222. $content = $this->getContent($request);
  223. // 댓글 기록 저장
  224. if($boardMeta->item('use_comment_history', 0)) {
  225. $this->commentHistoryModel->updater($request->comment, '댓글 수정');
  226. }
  227. // 댓글 수정
  228. $comment = $this->commentModel->updater($commentID, [
  229. 'board_id' => $boardID,
  230. 'post_id' => $postID,
  231. 'user_id' => $user?->id,
  232. 'content' => $content,
  233. 'is_secret' => $request->post('is_secret', 0),
  234. 'ip_address' => IP_ADDRESS,
  235. 'user_agent' => USER_AGENT,
  236. 'updated_at' => now()
  237. ]);
  238. // 댓글 작성시 글 수정 시각 갱신
  239. if($boardMeta->item('update_order_on_comment', 0)) {
  240. $this->postModel->updater($postID, [
  241. 'updated_at' => now()
  242. ]);
  243. }
  244. $response->comment = $comment;
  245. DB::commit();
  246. } catch (Exception $e) {
  247. $response = $response::fromException($e);
  248. DB::rollBack();
  249. }
  250. return $response;
  251. }
  252. /**
  253. * 댓글 답글
  254. */
  255. public function reply(CommentRequest $request, ResponseData $response): ResponseData
  256. {
  257. DB::beginTransaction();
  258. try {
  259. $user = $request->user();
  260. $boardMeta = $request->boardMeta;
  261. $boardID = intval($request->post('bid'));
  262. $postID = intval($request->post('pid'));
  263. $commentID = intval($request->post('cid'));
  264. $content = $this->getContent($request);
  265. // 답글 저장
  266. $newComment = $this->commentModel->reply($commentID, [
  267. 'board_id' => $boardID,
  268. 'post_id' => $postID,
  269. 'user_id' => $user?->id,
  270. 'parent_id' => null,
  271. 'is_reply' => 0,
  272. 'content' => $content,
  273. 'sid' => $user?->sid,
  274. 'username' => $request->post('username', $user?->name),
  275. 'email' => $user?->email,
  276. 'password' => $request->post('password'),
  277. 'reply' => 0,
  278. 'like' => 0,
  279. 'dislike' => 0,
  280. 'blame' => 0,
  281. 'device_type' => DEVICE_TYPE,
  282. 'is_secret' => ($boardMeta->item('use_comment_secret', 0) == '2' ? 1 : $request->post('is_secret', 0)),
  283. 'is_delete' => 0,
  284. 'ip_address' => IP_ADDRESS,
  285. 'user_agent' => USER_AGENT,
  286. 'deleted_at' => null,
  287. 'updated_at' => null,
  288. 'created_at' => now()
  289. ]);
  290. // 부모 댓글 답변 여부 등록
  291. $this->commentModel->updater($commentID, [
  292. 'is_reply' => 1
  293. ]);
  294. // 게시판 댓글 수 갱신
  295. $this->boardModel->updateCommentRows($boardID);
  296. // 게시글 댓글 수 갱신
  297. $this->postModel->updateCommentRows($postID);
  298. // 부모 댓글 수 갱신
  299. $this->commentModel->updateCommentRows($commentID);
  300. // 댓글 Meta 저장
  301. $this->commentMetaModel->save($newComment->id, [
  302. 'is_blind' => 0,
  303. 'exec_blind_key' => null
  304. ]);
  305. // 답글 등록 기록
  306. if($boardMeta->item('use_comment_history', 0)) {
  307. $this->commentHistoryModel->register($newComment, '답글 등록');
  308. }
  309. $response->comment = $newComment;
  310. DB::commit();
  311. } catch (Exception $e) {
  312. $response = $response::fromException($e);
  313. DB::rollBack();
  314. }
  315. return $response;
  316. }
  317. /**
  318. * 댓글 삭제
  319. */
  320. public function delete(Request $request, ResponseData $response): ResponseData
  321. {
  322. DB::beginTransaction();
  323. try {
  324. $comment = $request->comment;
  325. // 댓글 존재 확인
  326. if (!$comment->exists) {
  327. throw new Exception('댓글이 존재 하지 않습니다.');
  328. }
  329. $userID = $request->user()?->id;
  330. // 댓글 삭제 여부 승인
  331. $this->commentModel->updater($comment->id, [
  332. 'is_delete' => 1,
  333. 'deleted_at' => now()
  334. ]);
  335. // 댓글 삭제 정보 추가
  336. $this->commentDeletedModel->register([
  337. 'board_id' => $comment->board_id,
  338. 'post_id' => $comment->post_id,
  339. 'comment_id' => $comment->id,
  340. 'user_id' => $userID,
  341. 'ip_address' => IP_ADDRESS,
  342. 'user_agent' => USER_AGENT,
  343. 'created_at' => now()
  344. ]);
  345. // 게시판 댓글 수 갱신
  346. $this->boardModel->updateCommentRows($comment->board_id);
  347. // 게시판 댓글 수 갱신
  348. $this->postModel->updateCommentRows($comment->post_id);
  349. // 부모 댓글 수 갱신
  350. $this->commentModel->updateCommentRows($comment->id);
  351. $response->comment = $comment;
  352. DB::commit();
  353. } catch (Exception $e) {
  354. $response = $response::fromException($e);
  355. DB::rollBack();
  356. }
  357. return $response;
  358. }
  359. /**
  360. * 댓글 신고
  361. */
  362. public function blame(Request $request, ResponseData $response): ResponseData
  363. {
  364. DB::beginTransaction();
  365. try {
  366. $comment = $this->commentModel->findOrNew(
  367. $request->post('cid')
  368. );
  369. // 댓글 존재 확인
  370. if (!$comment->exists) {
  371. throw new Exception('댓글이 존재 하지 않습니다.');
  372. }
  373. $userID = $request->user()->id;
  374. // 이미 신고했는지 확인
  375. if ($this->commentBlameModel->isAlready($comment, $userID)) {
  376. throw new Exception('이미 신고하셨습니다.');
  377. }
  378. $this->commentBlameModel->register([
  379. 'board_id' => $comment->board_id,
  380. 'post_id' => $comment->post_id,
  381. 'comment_id' => $comment->id,
  382. 'user_id' => $userID,
  383. 'type' => $request->post('type'),
  384. 'reason' => $request->post('reason'),
  385. 'status' => 0,
  386. 'memo' => null,
  387. 'ip_address' => IP_ADDRESS,
  388. 'user_agent' => USER_AGENT,
  389. 'created_at' => now()
  390. ]);
  391. $this->commentModel->increaseBlame($comment->id);
  392. DB::commit();
  393. } catch (Exception $e) {
  394. $response = $response::fromException($e);
  395. DB::rollBack();
  396. }
  397. return $response;
  398. }
  399. /**
  400. * 댓글 좋아요
  401. */
  402. public function like(Request $request, ResponseData $response): ResponseData
  403. {
  404. DB::beginTransaction();
  405. try {
  406. $comment = $this->commentModel->findOrNew(
  407. $request->post('cid')
  408. );
  409. // 댓글 존재 확인
  410. if (!$comment->exists) {
  411. throw new Exception('댓글이 존재 하지 않습니다.');
  412. }
  413. $userID = $request->user()->id;
  414. $saveData = [
  415. 'board_id' => $comment->board_id,
  416. 'post_id' => $comment->post_id,
  417. 'comment_id' => $comment->id,
  418. 'user_id' => $userID,
  419. 'type' => LIKE,
  420. 'ip_address' => IP_ADDRESS,
  421. 'user_agent' => USER_AGENT,
  422. 'created_at' => now()
  423. ];
  424. $likeInfo = $this->commentLikeModel->info($comment, $userID);
  425. if ($likeInfo->exists) {
  426. $this->commentLikeModel->remove($comment, $userID);
  427. if ($likeInfo->type == LIKE) {
  428. $this->commentModel->decreaseLike($comment->id);
  429. } else if ($likeInfo->type == DISLIKE) {
  430. $this->commentLikeModel->register($saveData);
  431. $this->commentModel->increaseLike($comment->id);
  432. $this->commentModel->decreaseDisLike($comment->id);
  433. }
  434. } else {
  435. $this->commentLikeModel->register($saveData);
  436. $this->commentModel->increaseLike($comment->id);
  437. }
  438. $response->comment = $comment;
  439. DB::commit();
  440. } catch (Exception $e) {
  441. $response = $response::fromException($e);
  442. DB::rollBack();
  443. }
  444. return $response;
  445. }
  446. /**
  447. * 댓글 싫어요
  448. */
  449. public function dislike(Request $request, ResponseData $response): ResponseData
  450. {
  451. DB::beginTransaction();
  452. try {
  453. $comment = $this->commentModel->findOrNew(
  454. $request->post('cid')
  455. );
  456. // 댓글 존재 확인
  457. if (!$comment->exists) {
  458. throw new Exception('댓글이 존재 하지 않습니다.');
  459. }
  460. $userID = $request->user()->id;
  461. $saveData = [
  462. 'board_id' => $comment->board_id,
  463. 'post_id' => $comment->post_id,
  464. 'comment_id' => $comment->id,
  465. 'user_id' => $userID,
  466. 'type' => DISLIKE,
  467. 'ip_address' => IP_ADDRESS,
  468. 'user_agent' => USER_AGENT,
  469. 'created_at' => now()
  470. ];
  471. $likeInfo = $this->commentLikeModel->info($comment, $userID);
  472. if ($likeInfo->exists) {
  473. $this->commentLikeModel->remove($comment, $userID);
  474. if ($likeInfo->type == DISLIKE) {
  475. $this->commentModel->decreaseDisLike($comment->id);
  476. } else if ($likeInfo->type == LIKE) {
  477. $this->commentLikeModel->register($saveData);
  478. $this->commentModel->increaseDisLike($comment->id);
  479. $this->commentModel->decreaseLike($comment->id);
  480. }
  481. } else {
  482. $this->commentLikeModel->register($saveData);
  483. $this->commentModel->increaseDisLike($comment->id);
  484. }
  485. $response->comment = $comment;
  486. DB::commit();
  487. } catch (Exception $e) {
  488. $response = $response::fromException($e);
  489. DB::rollBack();
  490. }
  491. return $response;
  492. }
  493. /**
  494. * 게시글에 내 댓글이 몇개인지 확인
  495. */
  496. public function userCommentRows(Post $post, User $user): int
  497. {
  498. return $this->commentModel->where([
  499. ['board_id', $post->board_id],
  500. ['post_id', $post->id],
  501. ['user_id', $user->id],
  502. ])->count();
  503. }
  504. /**
  505. * 댓글 에디터 내용 처리
  506. */
  507. private function getContent(CommentRequest $request): string
  508. {
  509. // 게시글 이미지 저장
  510. $boardMeta = $request->boardMeta;
  511. $postID = $request->pid;
  512. $commentID = $this->commentModel->lastedKey();
  513. $content = trim($request->post('content'));
  514. if ($boardMeta->use_personal)
  515. {
  516. $addPath = ($postID . DIRECTORY_SEPARATOR . $commentID);
  517. if ($boardMeta->save_external_image) {
  518. // link
  519. $content = $this->fileLib->saveImageFromUrl($content, UPLOAD_PATH_COMMENT, $addPath);
  520. }
  521. // blob
  522. $content = $this->fileLib->saveImageFromBlob($content, UPLOAD_PATH_COMMENT, $addPath);
  523. // 이미지 DB 기록
  524. $images = $this->fileLib->getImageFromContent($content);
  525. if(count($images) > 0) {
  526. foreach($images as $img) {
  527. $this->editorImageModel->register([
  528. 'target_type' => EDITOR_IMG_TYPE_2,
  529. 'target_id' => $addPath,
  530. 'user_id' => UID,
  531. 'origin_name' => $img->origin,
  532. 'file_name' => $img->name,
  533. 'file_path' => $img->path,
  534. 'file_url' => $img->url,
  535. 'file_size' => $img->size,
  536. 'file_type' => $img->type,
  537. 'width' => $img->width,
  538. 'height' => $img->height,
  539. 'ip_address' => IP_ADDRESS,
  540. 'user_agent' => USER_AGENT
  541. ]);
  542. }
  543. }
  544. }
  545. return $content;
  546. }
  547. /**
  548. * 댓글 경험치 지급
  549. */
  550. public function setUserExp(Comment $comment, User $user, int $type): bool
  551. {
  552. $boardMeta = $this->boardMetaModel->getAllMeta($comment->board_id);
  553. if (!$boardMeta->item('use_exp', 0)) {
  554. return false;
  555. }
  556. $column = MAP_EXP_TYPE[$type];
  557. $content = MAP_EXP_CONTENT[$type];
  558. $value = intval($boardMeta->{$column});
  559. $relatedID = sprintf("%d-%d-%d", $comment->board_id, $comment->post_id, $comment->id);
  560. // 경험치 지급 구분
  561. return (new Exp)->register([
  562. 'user_id' => $user->id,
  563. 'content' => $content,
  564. 'value' => $value,
  565. 'type' => $type,
  566. 'related_id' => $relatedID,
  567. 'method' => request()->getMethod(),
  568. 'created_at' => now()
  569. ]);
  570. }
  571. /**
  572. * 게시글 관련 메일 발송
  573. */
  574. public function sendEmailNotify(Comment $comment, string $sendMailFormType): bool
  575. {
  576. if(!in_array($sendMailFormType, MAP_SEND_MAIL_TYPE)) {
  577. return false;
  578. }
  579. $post = $comment->post;
  580. $boardMeta = $this->boardMetaModel->getAllMeta($post->board_id);
  581. $sendMailType = str_replace('_form', '', $sendMailFormType);
  582. // 최고 관리자에게 발송
  583. $admin = ($boardMeta->item($sendMailType . '_super_admin', 0) ? $this->userModel->getMaster() : null);
  584. // 게시글 작성자에게 발송
  585. $postWriter = ($boardMeta->item($sendMailType . '_post_writer', 0) ? $post->user : null);
  586. // 댓글 작성자에게 발송
  587. $commentWriter = ($boardMeta->item($sendMailType . '_comment_writer', 0) ? $comment->user : null);
  588. if(!$admin && !$postWriter && !$commentWriter) {
  589. return false;
  590. }
  591. // 1:1 문의 답변 완료 처리
  592. $post->updater($post->id, [
  593. 'is_reply' => 1
  594. ]);
  595. return (new EmailLib)->send($sendMailFormType, $admin, $postWriter, $commentWriter);
  596. }
  597. }