Board.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. <?php
  2. namespace App\Models;
  3. use Illuminate\Http\Request;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\Database\Eloquent\Model;
  6. use App\Http\Traits\PagingTrait;
  7. use App\Http\Traits\BoardTrait;
  8. use App\Models\DTO\SearchData;
  9. use App\Models\DTO\ResponseData;
  10. use Exception;
  11. class Board extends Model
  12. {
  13. use PagingTrait;
  14. use BoardTrait;
  15. protected $table = 'tb_board';
  16. protected $primaryKey = 'id';
  17. public $keyType = 'int';
  18. public $incrementing = true;
  19. public $timestamps = true;
  20. const CREATED_AT = 'created_at';
  21. const UPDATED_AT = 'updated_at';
  22. protected $guarded = [];
  23. public function boardGroup()
  24. {
  25. return $this->belongsTo(BoardGroup::class)->withDefault();
  26. }
  27. public function boardCategory()
  28. {
  29. return $this->hasMany(BoardCategory::class);
  30. }
  31. public function boardMeta()
  32. {
  33. return $this->hasMany(BoardMeta::class);
  34. }
  35. public function boardAdmin()
  36. {
  37. return $this->hasMany(BoardAdmin::class);
  38. }
  39. public function post()
  40. {
  41. return $this->hasMany(Post::class);
  42. }
  43. /**
  44. * 게시판 조회
  45. */
  46. public function data(SearchData $params): object
  47. {
  48. $query = $this->query();
  49. $query->select(
  50. 'tb_board.*',
  51. DB::raw('(SELECT COUNT(*) FROM tb_post WHERE board_id = tb_board.id) AS postRows'),
  52. DB::raw('(SELECT COUNT(*) FROM tb_comment WHERE board_id = tb_board.id) AS commentRows')
  53. );
  54. if ($params->keyword) {
  55. switch ($params->field) {
  56. case 'tb_board.code':
  57. case 'tb_board.name':
  58. case 'users.name':
  59. case 'users.email':
  60. $query->where($params->field, 'LIKE', "%{$params->keyword}%");
  61. break;
  62. case 'tb_board.board_id':
  63. case 'users.id':
  64. case 'users.sid':
  65. $query->where($params->field, '=', $params->keyword);
  66. break;
  67. }
  68. }
  69. $query->orderBy('tb_board.sort');
  70. $list = $query->paginate($params->perPage, ['*'], 'page', $params->page);
  71. $total = $this->count();
  72. $rows = $list->count();
  73. return (object)[
  74. 'total' => $total,
  75. 'rows' => $rows,
  76. 'list' => $list
  77. ];
  78. }
  79. /**
  80. * 게시판 그룹에 조회
  81. */
  82. public function findByGroup(int $boardGroupID)
  83. {
  84. return static::where('board_group_id', $boardGroupID)->get();
  85. }
  86. /**
  87. * 게시판 주소로 게시판 정보 조회
  88. */
  89. public function findByCode(string $code): Board
  90. {
  91. return static::where('code', $code)->firstOrNew();
  92. }
  93. /**
  94. * 게시판 설정 등록
  95. */
  96. public function register(Request $request, ResponseData $response): ResponseData
  97. {
  98. DB::beginTransaction();
  99. try {
  100. $posts = $request->all();
  101. $boardID = $this->insertGetId([
  102. 'board_group_id' => $posts['board_group_id'],
  103. 'code' => $posts['code'],
  104. 'name' => $posts['name'],
  105. 'sort' => $posts['sort'],
  106. 'is_search' => $posts['is_search'],
  107. 'is_display' => $posts['is_display'],
  108. 'created_at' => now()
  109. ]);
  110. $boardMetaModel = new boardMeta();
  111. $boardMetaModel->save($boardID, [
  112. 'pc_header_content' => $posts['pc_header_content'],
  113. 'pc_footer_content' => $posts['pc_footer_content'],
  114. 'mobile_header_content' => $posts['mobile_header_content'],
  115. 'mobile_footer_content' => $posts['mobile_footer_content'],
  116. 'use_inform_modal' => $posts['use_inform_modal'],
  117. 'inform_content' => $posts['inform_content']
  118. ]);
  119. $tmpDefaultAdminAttributes = [
  120. // 목록
  121. 'board_layout_type' => [0, '게시판 종류'],
  122. 'list_sort_type' => [1, '기본 정렬 순'],
  123. 'list_per_page' => [15, 'PC - 목록 수'],
  124. 'list_mobile_per_page' => [15, '모바일 - 목록 수'],
  125. 'list_page_count' => [10, 'PC - 페이지 수'],
  126. 'list_page_mobile_count' => [10, '모바일 - 페이지 수'],
  127. 'always_show_write_button' => [1, '글쓰기 버튼 보이기'],
  128. 'show_list_from_view' => [1, '하단 목록 보이기'],
  129. 'new_icon_hour' => [0, 'NEW 아이콘 - 시간'],
  130. 'hot_icon_hit' => [0, 'HOT 아이콘 - 시간'],
  131. 'hot_icon_day' => [0, 'HOT 아이콘 - 일'],
  132. 'subject_length' => [0, 'PC - 제목'],
  133. 'subject_mobile_length' => [0, 'PC - 모바일'],
  134. 'except_notice' => [0, '공지사항 제외'],
  135. 'except_speaker' => [0, '전체공지 제외'],
  136. // 열람
  137. 'use_bookmark' => [1, '즐겨찾기 기능'],
  138. 'use_post_like' => [1, '추천 기능'],
  139. 'use_post_dislike' => [1, '비추천 기능'],
  140. 'use_print' => [1, '본문 인쇄 기능'],
  141. 'use_sns' => [1, 'SNS 보내기 기능'],
  142. 'use_prev_next_post' => [1, '이전글, 다음글 버튼'],
  143. 'use_blame' => [1, '신고 기능'],
  144. 'blame_blind_count' => [1, '신고 시 숨김'],
  145. 'show_post_ip' => [1, 'IP 보이기'],
  146. 'content_target_blank' => [1, 'Link 새창'],
  147. 'use_auto_url' => [1, '본문 URL Link 생성'],
  148. 'use_copy_post_url' => [1, '주소 복사 버튼'],
  149. 'use_url_qrcode' => [1, '글 주소 QR 코드'],
  150. 'use_attached_url_qrcode' => [1, '첨부된 Link QR 코드'],
  151. 'need_like_for_download' => [1, '다운로드 제한 (추천 필수)'],
  152. 'need_comment_for_download' => [1, '다운로드 제한 (댓글 필수)'],
  153. 'show_user_thumb_in_post' => [1, '회원 프로필 이미지'],
  154. 'show_user_icon_in_post' => [1, '회원 아이콘 이미지'],
  155. 'use_post_user_regdate' => [1, '회원 가입일'],
  156. // 작성
  157. 'write_header_content' => [null, '작성폼 상단 내용'],
  158. 'mobile_write_header_content' => [null, '모바일 작성폼 상단 내용'],
  159. 'post_default_subject' => [null, '글쓰기 시 기본 제목'],
  160. 'post_default_content' => [null, '글쓰기 시 기본 내용'],
  161. 'use_post_dhtml' => [1, '본문 에디터 사용'],
  162. 'save_external_image' => [0, '외부 이미지 가져오기'],
  163. 'post_subject_min_length' => [0, '최소 글수 제한 (제목)'],
  164. 'post_subject_max_length' => [0, '최대 글수 제한 (제목)'],
  165. 'post_content_min_length' => [0, '최소 글수 제한 (내용)'],
  166. 'post_content_max_length' => [0, '최대 글수 제한 (내용)'],
  167. 'use_category_required' => [0, '분류 필수 선택'],
  168. 'use_post_secret' => [0, '비밀글 사용'],
  169. 'use_post_secret_selected' => [0, '비밀글 기본 선택'],
  170. 'use_post_tag' => [0, '태그 사용'],
  171. 'link_num' => [0, 'URL Link 개수'],
  172. 'use_upload_file' => [0, '첨부파일 사용'],
  173. 'upload_file_num' => [0, '첨부파일 개수 제한'],
  174. 'upload_file_max_size' => [0, '첨부파일 용량 제한'],
  175. 'upload_only_img_file' => [0, '첨부파일 이미지 허용'],
  176. 'upload_file_extension' => [null, '첨부파일 허용 확장자'],
  177. 'use_only_one_post' => [0, '하루에 한 글만 작성'],
  178. 'use_post_captcha' => [0, 'Captcha 사용'],
  179. // 댓글
  180. 'use_comment' => [1, '댓글 사용 여부'],
  181. 'comment_per_page' => [20, '댓글 목록 수'],
  182. 'comment_page_count' => [10, '댓글 페이지 수'],
  183. 'use_comment_like' => [1, '댓글 추천 기능'],
  184. 'use_comment_dislike' => [1, '댓글 비추천 기능'],
  185. 'show_user_thumb_in_comment' => [1, '회원 프로필 이미지'],
  186. 'show_user_icon_in_comment' => [1, '회원 아이콘 이미지'],
  187. 'update_order_on_comment' => [1, '댓글 작성시 글 수정 시각 갱신'],
  188. 'comment_default_content' => [null, '댓글 기본 내용'],
  189. 'comment_min_length' => [0, '최소 글수 제한'],
  190. 'comment_max_length' => [0, '최대 글수 제한'],
  191. 'use_comment_secret' => [0, '비밀글 사용'],
  192. 'use_comment_secret_selected' => [0, '비밀글 기본 선택'],
  193. 'show_comment_ip' => [1, 'IP 보이기'],
  194. 'use_comment_blame' => [1, '댓글 신고 기능'],
  195. 'comment_blame_blind_count' => [1, '댓글 신고 시 숨김'],
  196. 'protect_delete_comment' => [0, '댓글 보호 기능 (삭제 시)'],
  197. 'protect_update_comment' => [0, '댓글 보호 기능 (수정 시)'],
  198. // 일반
  199. 'block_delete' => [0, '관리자만 삭제'],
  200. 'protect_post_day' => [0, '게시글 수정/삭제 금지 기간'],
  201. 'protect_comment_day' => [0, '댓글 수정/삭제 금지 기간'],
  202. 'protect_delete_post' => [0, '게시글 보호 기능 (삭제 시)'],
  203. 'protect_update_post' => [0, '게시글 보호 기능 (수정 시)'],
  204. 'use_category' => [0, '분류 기능'],
  205. 'use_personal' => [0, '1:1 게시판'],
  206. 'use_anonymous' => [0, '익명 게시판'],
  207. 'anonymous_except_admin' => [0, '관리자 익명 제외'],
  208. 'anonymous_name' => ['$NUM', '익명 이름'],
  209. 'use_download_log' => [0, '다운로드 기록'],
  210. 'use_post_history' => [1, '게시물 변경 기록'],
  211. 'use_link_click_log' => [1, 'Link 클릭 기록'],
  212. 'use_comment_history' => [1, '댓글 변경 기록'],
  213. // 알람
  214. 'send_email_post_super_admin' => [0, '이메일 발송(원글 작성) - 최고 관리자'],
  215. 'send_email_post_writer' => [0, '이메일 발송(원글 작성) - 원글 작성자'],
  216. 'send_email_comment_super_admin' => [0, '이메일 발송(댓글 작성) - 최고 관리자'],
  217. 'send_email_comment_post_writer' => [0, '이메일 발송(댓글 작성) - 원글 작성자'],
  218. 'send_email_comment_comment_writer' => [0, '이메일 발송(댓글) - 댓글 작성자'],
  219. 'send_email_blame_super_admin' => [0, '이메일 발송(원글 신고) - 최고 관리자'],
  220. 'send_email_blame_post_writer' => [0, '이메일 발송(원글 신고) - 원글 작성자'],
  221. 'send_email_comment_blame_super_admin' => [0, '이메일 발송(댓글 신고) - 최고 관리자'],
  222. 'send_email_comment_blame_post_writer' => [0, '이메일 발송(댓글 신고) - 원글 작성자'],
  223. 'send_email_comment_blame_comment_writer' => [0, '이메일 발송(댓글 신고) - 댓글 작성자'],
  224. 'send_telegram_post_super_admin' => [0, '텔레그램 발송 (원글 작성 시)'],
  225. 'send_telegram_comment_super_admin' => [0, '텔레그램 발송 (댓글 작성 시)'],
  226. 'send_telegram_blame_super_admin' => [0, '텔레그램 발송 (원글 신고 발생 시)'],
  227. 'send_telegram_comment_blame_super_admin' => [0, '텔레그램 발송 (댓글 신고 발생 시)'],
  228. // 권한
  229. 'access_post_list' => [null, '목록'],
  230. 'access_post_view' => [null, '글 열람'],
  231. 'access_post_write' => [null, '글 작성'],
  232. 'access_comment_list' => [null, '댓글 목록'],
  233. 'access_comment_write' => [null, '댓글 작성'],
  234. 'access_file_upload' => [null, '파일 업로드'],
  235. 'access_file_download' => [null, '파일 다운로드']
  236. ];
  237. /*
  238. * 기본 정보 저장
  239. */
  240. if($tmpDefaultAdminAttributes) {
  241. foreach ($tmpDefaultAdminAttributes as $attribute => $row) {
  242. $boardMetaModel->save($boardID, [
  243. $attribute => $row[0]
  244. ]);
  245. }
  246. }
  247. /*
  248. * 그룹, 전체 적용
  249. */
  250. $groupData = []; // 그룹적용
  251. $allData = []; // 전체적용
  252. $defaultData = [ // 그룹, 전체 적용 값
  253. 'pc_header_content', 'pc_footer_content',
  254. 'mobile_header_content', 'mobile_footer_content',
  255. 'use_inform_modal', 'inform_content'
  256. ];
  257. foreach ($defaultData as $field) {
  258. if (isset($posts['grp'][$field])) {
  259. $groupData[$field] = $posts[$field];
  260. }
  261. if (isset($posts['all'][$field])) {
  262. $allData[$field] = $posts[$field];
  263. }
  264. }
  265. if ($groupData) {
  266. $brdGroupData = $this->findByGroup($posts['board_group_id']);
  267. foreach ($brdGroupData as $bKey => $bVal) {
  268. if ($bVal->id === $boardID) {
  269. continue;
  270. }
  271. $boardMetaModel->save($bVal->id, $groupData);
  272. }
  273. }
  274. if ($allData) {
  275. $brdAllData = $this->all();
  276. foreach ($brdAllData as $bKey => $bVal) {
  277. if ($bVal->id === $boardID) {
  278. continue;
  279. }
  280. $boardMetaModel->save($bVal->id, $allData);
  281. }
  282. }
  283. DB::commit();
  284. } catch (Exception $e) {
  285. $response = $response::fromException($e);
  286. DB::rollBack();
  287. }
  288. return $response;
  289. }
  290. /**
  291. * 게시판 설정 수정
  292. */
  293. public function updater(int $boardID, Request $request, ResponseData $response): ResponseData
  294. {
  295. DB::beginTransaction();
  296. try {
  297. $posts = $request->all();
  298. $this->find($boardID)->update([
  299. 'board_group_id' => $posts['board_group_id'],
  300. 'code' => $posts['code'],
  301. 'name' => $posts['name'],
  302. 'sort' => $posts['sort'],
  303. 'is_search' => $posts['is_search'],
  304. 'is_display' => $posts['is_display'],
  305. 'updated_at' => now()
  306. ]);
  307. $boardMetaModel = new boardMeta();
  308. $boardMetaModel->save($boardID, [
  309. 'pc_header_content' => $posts['pc_header_content'],
  310. 'pc_footer_content' => $posts['pc_footer_content'],
  311. 'mobile_header_content' => $posts['mobile_header_content'],
  312. 'mobile_footer_content' => $posts['mobile_footer_content'],
  313. 'use_inform_modal' => $posts['use_inform_modal'],
  314. 'inform_content' => $posts['inform_content']
  315. ]);
  316. /*
  317. * 그룹, 전체 적용
  318. */
  319. $groupData = []; // 그룹적용
  320. $allData = []; // 전체적용
  321. $defaultData = [ // 그룹, 전체 적용 값
  322. 'pc_header_content', 'pc_footer_content',
  323. 'mobile_header_content', 'mobile_footer_content',
  324. 'use_inform_modal', 'inform_content'
  325. ];
  326. foreach ($defaultData as $field) {
  327. if (isset($posts['grp'][$field])) {
  328. $groupData[$field] = $posts[$field];
  329. }
  330. if (isset($posts['all'][$field])) {
  331. $allData[$field] = $posts[$field];
  332. }
  333. }
  334. if ($groupData) {
  335. $brdGroupData = $this->findByGroup($posts['board_group_id']);
  336. foreach ($brdGroupData as $bKey => $bVal) {
  337. if ($bVal->id === $boardID) {
  338. continue;
  339. }
  340. $boardMetaModel->save($bVal->id, $groupData);
  341. }
  342. }
  343. if ($allData) {
  344. $brdAllData = $this->all();
  345. foreach ($brdAllData as $bKey => $bVal) {
  346. if ($bVal->id === $boardID) {
  347. continue;
  348. }
  349. $boardMetaModel->save($bVal->id, $allData);
  350. }
  351. }
  352. DB::commit();
  353. } catch (Exception $e) {
  354. $response = $response::fromException($e);
  355. DB::rollBack();
  356. }
  357. return $response;
  358. }
  359. /**
  360. * 게시판 공지글 조회
  361. * 공지글은 최신순으로 정렬
  362. */
  363. public function getNotices(SearchData $params): object
  364. {
  365. $sql = "
  366. SELECT
  367. COUNT(*) AS total
  368. FROM tb_post PST
  369. JOIN tb_board BRD ON BRD.id = PST.board_id
  370. WHERE
  371. BRD.code = ? AND BRD.is_display = 1 AND PST.is_notice = 1 AND PST.is_delete = 0 OR
  372. PST.id IN (SELECT id FROM tb_post WHERE PST.is_speaker = 1 AND PST.is_delete = 0);
  373. ";
  374. $total = DB::selectOne($sql, [$params->code])->total;
  375. $sql = "
  376. SELECT
  377. BRD.code, BRD.name AS boardName,
  378. BCA.name AS categoryName,
  379. PST.id, PST.board_id, PST.board_category_id, PST.user_id, PST.thumbnail, PST.subject, PST.content,
  380. PST.sid, PST.username, PST.email, PST.comment_rows, PST.is_reply, PST.is_personal, PST.is_secret,
  381. PST.is_notice, PST.is_speaker, PST.is_html, PST.use_comment, PST.receive_email, PST.hit, PST.like, PST.dislike,
  382. PST.blame, PST.ip_address, PST.user_agent, PST.device_type, PST.file_rows, PST.image_rows,
  383. PST.link_rows, PST.tag_rows, PST.is_delete, PST.created_at
  384. FROM tb_post PST
  385. INNER JOIN tb_board BRD ON BRD.id = PST.board_id
  386. LEFT JOIN tb_board_category BCA ON BCA.id = PST.board_category_id
  387. LEFT JOIN users USR ON USR.id = PST.user_id
  388. WHERE
  389. BRD.code = ? AND BRD.is_display = 1 AND PST.is_notice = 1 AND PST.is_delete = 0 OR
  390. PST.id IN (SELECT id FROM tb_post WHERE PST.is_speaker = 1 AND PST.is_delete = 0)
  391. ORDER BY
  392. PST.id DESC,
  393. PST.created_at DESC;
  394. ";
  395. $list = $this->setRawAttributes(DB::select($sql, [$params->code]))->getAttributes();
  396. return (object)[
  397. 'total' => $total,
  398. 'list' => $list
  399. ];
  400. }
  401. /**
  402. * 게시글 목록 조회
  403. */
  404. public function getPosts(SearchData $params): object
  405. {
  406. // 검색 쿼리 생성
  407. $where = $this->buildWhereQuery($params);
  408. // 정렬 기준 생성
  409. $orderBy = $this->sortQuery($params);
  410. $sql = sprintf("
  411. SELECT
  412. COUNT(*) AS total
  413. FROM tb_post PST
  414. JOIN tb_board BRD ON BRD.id = PST.board_id
  415. WHERE BRD.code = ? AND BRD.is_display = 1 AND PST.is_notice = 0 AND PST.is_speaker = 0 AND PST.is_delete = 0
  416. %s;
  417. ", $where);
  418. $total = DB::selectOne($sql, [$params->code])->total;
  419. $sql = sprintf("
  420. SELECT
  421. BRD.code, BRD.name AS boardName,
  422. BCA.name AS categoryName,
  423. PST.id, PST.board_id, PST.board_category_id, PST.user_id, PST.thumbnail, PST.subject, PST.content,
  424. PST.sid, PST.username, PST.email, PST.comment_rows, PST.is_reply, PST.is_personal, PST.is_secret,
  425. PST.is_notice, PST.is_speaker, PST.is_html, PST.use_comment, PST.receive_email, PST.hit, PST.like, PST.dislike,
  426. PST.blame, PST.ip_address, PST.user_agent, PST.device_type, PST.file_rows, PST.image_rows,
  427. PST.link_rows, PST.tag_rows, PST.is_delete, PST.created_at
  428. FROM tb_post PST
  429. INNER JOIN tb_board BRD ON BRD.id = PST.board_id
  430. LEFT JOIN tb_board_category BCA ON BCA.id = PST.board_category_id
  431. LEFT JOIN users USR ON USR.id = PST.user_id
  432. WHERE BRD.code = ? AND BRD.is_display = 1 AND PST.is_notice = 0 AND PST.is_speaker = 0 AND PST.is_delete = 0
  433. %s
  434. ORDER BY
  435. %s
  436. LIMIT ?, ?;
  437. ", $where, $orderBy);
  438. $list = $this->getPaginator(
  439. DB::select($sql, [$params->code, $params->offset, $params->perPage]),
  440. $total, $params->perPage, $params->page, $params->path
  441. );
  442. return (object)[
  443. 'total' => $total,
  444. 'list' => $list
  445. ];
  446. }
  447. /**
  448. * 게시글 목록 조회
  449. */
  450. public function getUserPosts(int $userID, SearchData $params): object
  451. {
  452. $sql = "
  453. SELECT
  454. COUNT(*) AS total
  455. FROM tb_post PST
  456. JOIN tb_board BRD ON BRD.id = PST.board_id
  457. WHERE
  458. BRD.is_display = 1 AND PST.is_notice = 0 AND PST.is_speaker = 0 AND PST.is_delete = 0 AND PST.user_id = ? AND PST.is_personal = 0;
  459. ";
  460. $total = DB::selectOne($sql, [$userID])->total;
  461. $sql = "
  462. SELECT
  463. BRD.code, BRD.name AS boardName,
  464. BCA.name AS categoryName,
  465. PST.id, PST.board_id, PST.board_category_id, PST.user_id, PST.thumbnail, PST.subject, PST.content,
  466. PST.sid, PST.username, PST.email, PST.comment_rows, PST.is_reply, PST.is_personal, PST.is_secret,
  467. PST.is_notice, PST.is_speaker, PST.is_html, PST.use_comment, PST.receive_email, PST.hit, PST.like, PST.dislike,
  468. PST.blame, PST.ip_address, PST.user_agent, PST.device_type, PST.file_rows, PST.image_rows,
  469. PST.link_rows, PST.tag_rows, PST.is_delete, PST.created_at
  470. FROM tb_post PST
  471. INNER JOIN tb_board BRD ON BRD.id = PST.board_id
  472. LEFT JOIN tb_board_category BCA ON BCA.id = PST.board_category_id
  473. LEFT JOIN users USR ON USR.id = PST.user_id
  474. WHERE
  475. BRD.is_display = 1 AND PST.is_notice = 0 AND PST.is_speaker = 0 AND PST.is_delete = 0 AND PST.user_id = ? AND PST.is_personal = 0
  476. ORDER BY
  477. PST.created_at DESC, PST.id DESC
  478. LIMIT ?, ?;
  479. ";
  480. $list = $this->getPaginator(
  481. DB::select($sql, [$userID, $params->offset, $params->perPage]),
  482. $total, $params->perPage, $params->page, $params->path
  483. );
  484. return (object)[
  485. 'total' => $total,
  486. 'list' => $list
  487. ];
  488. }
  489. /**
  490. * 게시판 최근 게시글 조회
  491. */
  492. public function getLatest(?string $code, int $page, int $perPage): object
  493. {
  494. $sql = "
  495. SELECT
  496. COUNT(*) AS total
  497. FROM
  498. (SELECT @code := ?) V,
  499. tb_post PST
  500. JOIN tb_board BRD ON BRD.id = PST.board_id
  501. WHERE
  502. CASE WHEN @code IS NOT NULL THEN BRD.code = @code ELSE (PST.is_notice = 0 AND PST.is_speaker = 0) END
  503. AND BRD.is_display = 1 AND PST.is_delete = 0;
  504. ";
  505. $total = DB::selectOne($sql, [$code])->total;
  506. $sql = "
  507. SELECT
  508. BRD.code, BRD.name AS boardName,
  509. BCA.name AS categoryName,
  510. PST.id, PST.board_id, PST.board_category_id, PST.user_id, PST.thumbnail, PST.subject, PST.content,
  511. PST.sid, PST.username, PST.email, PST.comment_rows, PST.is_reply, PST.is_personal, PST.is_secret,
  512. PST.is_notice, PST.is_speaker, PST.is_html, PST.use_comment, PST.receive_email, PST.hit, PST.like, PST.dislike,
  513. PST.blame, PST.ip_address, PST.user_agent, PST.device_type, PST.file_rows, PST.image_rows,
  514. PST.link_rows, PST.tag_rows, PST.is_delete, PST.created_at
  515. FROM
  516. (SELECT @code := ?) V,
  517. tb_post PST
  518. INNER JOIN tb_board BRD ON BRD.id = PST.board_id
  519. LEFT JOIN tb_board_category BCA ON BCA.id = PST.board_category_id
  520. LEFT JOIN users USR ON USR.id = PST.user_id
  521. WHERE
  522. CASE WHEN @code IS NOT NULL THEN BRD.code = @code ELSE (PST.is_notice = 0 AND PST.is_speaker = 0) END
  523. AND BRD.is_display = 1 AND PST.is_delete = 0
  524. ORDER BY
  525. PST.created_at DESC, PST.id DESC
  526. LIMIT ?, ?;
  527. ";
  528. $list = $this->getPaginator(
  529. DB::select($sql, [$code, $this->getPageOffset($page), $perPage]), $total, $perPage, $page,
  530. ($code ? route('board.list', $code) : null)
  531. );
  532. return (object)[
  533. 'total' => $total,
  534. 'list' => $list
  535. ];
  536. }
  537. /**
  538. * 게시판 최근 게시글 조회
  539. */
  540. public function getSearch(SearchData $params): object
  541. {
  542. // 검색 쿼리 생성
  543. $where = $this->buildWhereQuery($params);
  544. // 정렬 기준 생성
  545. $orderBy = $this->sortQuery($params);
  546. $sql = sprintf("
  547. SELECT
  548. COUNT(*) AS total
  549. FROM
  550. tb_post PST
  551. JOIN tb_board BRD ON BRD.id = PST.board_id
  552. WHERE TRUE AND BRD.is_display = 1 AND PST.is_notice = 0 AND PST.is_speaker = 0 AND PST.is_delete = 0
  553. %s;
  554. ", $where);
  555. $total = DB::selectOne($sql)->total;
  556. $sql = sprintf("
  557. SELECT
  558. BRD.code, BRD.name AS boardName,
  559. BCA.name AS categoryName,
  560. PST.id, PST.board_id, PST.board_category_id, PST.user_id, PST.thumbnail, PST.subject, PST.content,
  561. PST.sid, PST.username, PST.email, PST.comment_rows, PST.is_reply, PST.is_personal, PST.is_secret,
  562. PST.is_notice, PST.is_speaker, PST.is_html, PST.use_comment, PST.receive_email, PST.hit, PST.like, PST.dislike,
  563. PST.blame, PST.ip_address, PST.user_agent, PST.device_type, PST.file_rows, PST.image_rows,
  564. PST.link_rows, PST.tag_rows, PST.is_delete, PST.created_at
  565. FROM
  566. tb_post PST
  567. INNER JOIN tb_board BRD ON BRD.id = PST.board_id
  568. LEFT JOIN tb_board_category BCA ON BCA.id = PST.board_category_id
  569. LEFT JOIN users USR ON USR.id = PST.user_id
  570. WHERE TRUE AND BRD.is_display = 1 AND PST.is_notice = 0 AND PST.is_speaker = 0 AND PST.is_delete = 0
  571. %s
  572. ORDER BY
  573. %s
  574. LIMIT ?, ?;
  575. ", $where, $orderBy);
  576. $list = $this->getPaginator(
  577. DB::select($sql, [$params->offset, $params->perPage]), $total, $params->perPage, $params->page
  578. );
  579. return (object)[
  580. 'total' => $total,
  581. 'list' => $list
  582. ];
  583. }
  584. /**
  585. * 게시판 검색 쿼리 생성
  586. */
  587. private function buildWhereQuery(?SearchData $params): string
  588. {
  589. $sql = "";
  590. if(!$params) {
  591. return $sql;
  592. }
  593. // 분류 지정
  594. if($params->category) {
  595. $sql .= sprintf(" AND PST.board_category_id = %d", $params->category);
  596. }
  597. // 검색
  598. if($params->keyword) {
  599. $sql .= " AND BRD.is_search = 1 ";
  600. switch ($params->field) {
  601. case 1 : // 제목 + 내용
  602. $sql .= sprintf("AND (PST.subject LIKE '%%%s%%' OR PST.content LIKE '%%%s%%')", $params->keyword, $params->keyword);
  603. break;
  604. case 2 : // 제목
  605. $sql .= sprintf("AND PST.subject LIKE '%%%s%%'", $params->keyword);
  606. break;
  607. case 3 : // 글 작성자
  608. $sql .= sprintf("AND (PST.username LIKE '%%%s%%' OR PST.nickname LIKE '%%%s%%')", $params->keyword);
  609. break;
  610. case 4 : // 댓글 내용
  611. $sql .= sprintf("AND PST.id IN (SELECT post_id FROM tb_comment WHERE content LIKE '%%%s%%')", $params->keyword);
  612. break;
  613. case 5 : // 댓글 작성자
  614. $sql .= sprintf("AND PST.id IN (SELECT post_id FROM tb_comment WHERE (username LIKE '%%%s%%' OR nickname LIKE '%%%s%%'))", $params->keyword, $params->keyword);
  615. break;
  616. }
  617. }
  618. if($params->code) {
  619. $boardMeta = $params->boardMeta;
  620. // 1:1 문의 회원 번호 조회
  621. if ($boardMeta->item('use_personal', 0)) {
  622. $sql .= sprintf(" AND PST.user_id = %d", UID);
  623. }
  624. }
  625. return $sql;
  626. }
  627. /**
  628. * 게시판 정렬 방법
  629. */
  630. private function sortQuery(?SearchData $params): string
  631. {
  632. $sql = 'PST.created_at DESC, PST.id DESC';
  633. if(!$params) {
  634. return $sql;
  635. }
  636. switch($params->sort) {
  637. default:case 1: // 날짜순
  638. return $sql;
  639. case 2: // 조회수
  640. return 'PST.hit DESC, PST.id DESC';
  641. case 3: // 댓글순
  642. return 'PST.comment_rows DESC, PST.id DESC';
  643. case 4: // 추천순
  644. return 'PST.like DESC, PST.id DESC';
  645. }
  646. }
  647. /**
  648. * 게시글 수 갱신
  649. */
  650. public function updatePostRows(int $boardID): int
  651. {
  652. $sql = '
  653. UPDATE tb_board A
  654. SET A.post_rows = (
  655. (SELECT COUNT(*) AS total FROM tb_post WHERE board_id = A.id AND is_delete = 0)
  656. )
  657. WHERE A.id = ?;
  658. ';
  659. return DB::update($sql, [$boardID]);
  660. }
  661. /**
  662. * 댓글 개수 갱신
  663. */
  664. public function updateCommentRows(int $boardID): int
  665. {
  666. $sql = '
  667. UPDATE tb_board A
  668. SET A.comment_rows = (
  669. SELECT COUNT(*) AS total FROM tb_comment CMT WHERE CMT.board_id = A.id AND
  670. CASE WHEN
  671. (
  672. CMT.is_delete = 1
  673. AND ((SELECT (COUNT(*) - 1) FROM tb_comment WHERE lft BETWEEN CMT.lft AND CMT.rgt) <= 0)
  674. AND ((SELECT (COUNT(*) - 1) FROM tb_comment WHERE CMT.rgt BETWEEN lft AND rgt) <= 0)
  675. ) THEN FALSE ELSE TRUE END
  676. )
  677. WHERE A.id = ?;
  678. ';
  679. return DB::update($sql, [$boardID]);
  680. }
  681. /**
  682. * 삭제
  683. */
  684. public function remove(int $boardID): mixed
  685. {
  686. return DB::transaction(function() use($boardID) {
  687. $board = $this->findOrNew($boardID);
  688. if($board->exists) {
  689. self::deleting(function($board)
  690. {
  691. // 게시판 분류 삭제
  692. $board->boardCategory()->each(function($category) {
  693. $category->delete();
  694. });
  695. // 메타 삭제
  696. $board->boardMeta()->each(function($meta) {
  697. $meta->delete();
  698. });
  699. // 관리자 삭제
  700. $board->boardAdmin()->each(function($admin) {
  701. $admin->delete();
  702. });
  703. // 게시글 삭제
  704. $board->post()->each(function($post) {
  705. $post->delete();
  706. });
  707. });
  708. $board->delete();
  709. }
  710. return true;
  711. });
  712. }
  713. }