Просмотр файла app/Http/Controllers/Forum/TopicController.php

Размер файла: 22.53Kb
  1. <?php
  2.  
  3. declare(strict_types=1);
  4.  
  5. namespace App\Http\Controllers\Forum;
  6.  
  7. use App\Http\Controllers\Controller;
  8. use App\Models\File;
  9. use App\Models\Polling;
  10. use App\Models\Post;
  11. use App\Models\Bookmark;
  12. use App\Models\Reader;
  13. use App\Models\Topic;
  14. use App\Models\User;
  15. use App\Models\Vote;
  16. use App\Models\Flood;
  17. use App\Classes\Validator;
  18. use App\Models\VoteAnswer;
  19. use Illuminate\Http\RedirectResponse;
  20. use Illuminate\Support\Facades\DB;
  21. use Illuminate\Database\Eloquent\Builder;
  22. use Illuminate\Database\Query\JoinClause;
  23. use Illuminate\Http\Request;
  24. use Illuminate\Support\Arr;
  25. use Illuminate\View\View;
  26.  
  27. class TopicController extends Controller
  28. {
  29. /**
  30. * Main page
  31. *
  32. * @param int $id
  33. *
  34. * @return View
  35. */
  36. public function index(int $id): View
  37. {
  38. $user = getUser();
  39.  
  40. $topic = Topic::query()
  41. ->where('topics.id', $id)
  42. ->when($user, static function (Builder $query) use ($user) {
  43. $query->select('topics.*', 'bookmarks.count_posts as bookmark_posts')
  44. ->leftJoin('bookmarks', static function (JoinClause $join) use ($user) {
  45. $join->on('topics.id', 'bookmarks.topic_id')
  46. ->where('bookmarks.user_id', $user->id);
  47. });
  48. })
  49. ->with('forum.parent')
  50. ->first();
  51.  
  52. if (! $topic) {
  53. abort(404, __('forums.topic_not_exist'));
  54. }
  55.  
  56. $posts = Post::query()
  57. ->where('topic_id', $topic->id)
  58. ->when($user, static function (Builder $query) use ($user) {
  59. $query->select('posts.*', 'pollings.vote')
  60. ->leftJoin('pollings', static function (JoinClause $join) use ($user) {
  61. $join->on('posts.id', 'pollings.relate_id')
  62. ->where('pollings.relate_type', Post::$morphName)
  63. ->where('pollings.user_id', $user->id);
  64. });
  65. })
  66. ->with('files', 'user', 'editUser')
  67. ->orderBy('created_at')
  68. ->paginate(setting('forumpost'));
  69.  
  70. if ($posts->onFirstPage()) {
  71. $firstPost = $posts->first();
  72. } else {
  73. $firstPost = Post::query()->where('topic_id', $topic->id)->orderBy('created_at')->first();
  74. }
  75.  
  76. if (isset($topic->bookmark_posts) && $topic->count_posts > $topic->bookmark_posts) {
  77. Bookmark::query()
  78. ->where('topic_id', $topic->id)
  79. ->where('user_id', $user->id)
  80. ->update(['count_posts' => $topic->count_posts]);
  81. }
  82.  
  83. // Curators
  84. if ($topic->moderators) {
  85. $topic->curators = User::query()->whereIn('login', explode(',', (string) $topic->moderators))->get();
  86. $topic->isModer = $user ? $topic->curators->where('id', $user->id)->isNotEmpty() : false;
  87. }
  88.  
  89. // Visits
  90. Reader::countingStat($topic);
  91.  
  92. // Votes
  93. $vote = Vote::query()->where('topic_id', $topic->id)->first();
  94.  
  95. if ($vote) {
  96. $vote->poll = $vote->pollings()
  97. ->where('user_id', $user->id ?? null)
  98. ->first();
  99.  
  100. if ($vote->answers->isNotEmpty()) {
  101. $results = Arr::pluck($vote->answers, 'result', 'answer');
  102. $max = max($results);
  103.  
  104. arsort($results);
  105.  
  106. $vote->voted = $results;
  107.  
  108. $vote->sum = ($vote->count > 0) ? $vote->count : 1;
  109. $vote->max = ($max > 0) ? $max : 1;
  110. }
  111. }
  112.  
  113. $files = [];
  114. $description = $firstPost ? truncateDescription(bbCode($firstPost->text, false)) : $topic->title;
  115.  
  116. if ($user) {
  117. $files = File::query()
  118. ->where('relate_type', Post::$morphName)
  119. ->where('relate_id', 0)
  120. ->where('user_id', $user->id)
  121. ->orderBy('created_at')
  122. ->get();
  123. }
  124.  
  125. return view('forums/topic', compact('topic', 'posts', 'vote', 'description', 'files'));
  126. }
  127.  
  128. /**
  129. * Message creation
  130. *
  131. * @param int $id
  132. * @param Request $request
  133. * @param Validator $validator
  134. * @param Flood $flood
  135. *
  136. * @return RedirectResponse
  137. */
  138. public function create(int $id, Request $request, Validator $validator, Flood $flood): RedirectResponse
  139. {
  140. $msg = $request->input('msg');
  141.  
  142. if (! $user = getUser()) {
  143. abort(403, __('main.not_authorized'));
  144. }
  145.  
  146. $topic = Topic::query()
  147. ->select('topics.*', 'forums.parent_id')
  148. ->where('topics.id', $id)
  149. ->leftJoin('forums', 'topics.forum_id', 'forums.id')
  150. ->first();
  151.  
  152. if (! $topic) {
  153. abort(404, __('forums.topic_not_exist'));
  154. }
  155.  
  156. $validator->equal($request->input('_token'), csrf_token(), ['msg' => __('validator.token')])
  157. ->empty($topic->closed, ['msg' => __('forums.topic_closed')])
  158. ->false($flood->isFlood(), ['msg' => __('validator.flood', ['sec' => $flood->getPeriod()])])
  159. ->length($msg, 5, setting('forumtextlength'), ['msg' => __('validator.text')]);
  160.  
  161. // Проверка сообщения на схожесть
  162. /** @var Post $post */
  163. $post = Post::query()->where('topic_id', $topic->id)->orderByDesc('id')->first();
  164. $validator->notEqual($msg, $post->text, ['msg' => __('forums.post_repeat')]);
  165.  
  166. if ($validator->isValid()) {
  167. $msg = antimat($msg);
  168.  
  169. $countFiles = File::query()
  170. ->where('relate_type', Post::$morphName)
  171. ->where('relate_id', 0)
  172. ->where('user_id', $user->id)
  173. ->orderBy('created_at')
  174. ->count();
  175.  
  176. if (
  177. $post
  178. && $post->created_at + 600 > SITETIME
  179. && $user->id === $post->user_id
  180. && $countFiles + $post->files->count() <= setting('maxfiles')
  181. && (utfStrlen($msg) + utfStrlen($post->text) <= setting('forumtextlength'))
  182. ) {
  183. $newpost = $post->text
  184. . PHP_EOL
  185. . PHP_EOL
  186. . '[i][size=1]'
  187. . __('forums.post_added_after', ['sec' => makeTime(SITETIME - $post->created_at)])
  188. . '[/size][/i]'
  189. . PHP_EOL
  190. . $msg;
  191.  
  192. $post->update(['text' => $newpost]);
  193. } else {
  194. $post = Post::query()->create([
  195. 'topic_id' => $topic->id,
  196. 'user_id' => $user->id,
  197. 'text' => $msg,
  198. 'created_at' => SITETIME,
  199. 'ip' => getIp(),
  200. 'brow' => getBrowser(),
  201. ]);
  202.  
  203. $user->increment('allforum');
  204. $user->increment('point');
  205. $user->increment('money', 5);
  206.  
  207. $topic->update([
  208. 'count_posts' => DB::raw('count_posts + 1'),
  209. 'last_post_id' => $post->id,
  210. 'updated_at' => SITETIME,
  211. ]);
  212.  
  213. $topic->forum->update([
  214. 'count_posts' => DB::raw('count_posts + 1'),
  215. 'last_topic_id' => $topic->id,
  216. ]);
  217.  
  218. // Обновление родительского форума
  219. if ($topic->forum->parent_id) {
  220. $topic->forum->parent->update([
  221. 'last_topic_id' => $topic->id,
  222. ]);
  223. }
  224. }
  225.  
  226. File::query()
  227. ->where('relate_type', Post::$morphName)
  228. ->where('relate_id', 0)
  229. ->where('user_id', $user->id)
  230. ->update(['relate_id' => $post->id]);
  231.  
  232. clearCache(['statForums', 'recentTopics', 'TopicFeed']);
  233. $flood->saveState();
  234. sendNotify($msg, '/topics/' . $topic->id . '/' . $post->id, $topic->title);
  235.  
  236. setFlash('success', __('main.message_added_success'));
  237. } else {
  238. setInput($request->all());
  239. setFlash('danger', $validator->getErrors());
  240. }
  241.  
  242. return redirect('topics/end/' . $topic->id);
  243. }
  244.  
  245. /**
  246. * Delete messages
  247. *
  248. * @param int $id
  249. * @param Request $request
  250. * @param Validator $validator
  251. *
  252. * @return RedirectResponse
  253. */
  254. public function delete(int $id, Request $request, Validator $validator): RedirectResponse
  255. {
  256. $del = intar($request->input('del'));
  257. $page = int($request->input('page'));
  258.  
  259. if (! $user = getUser()) {
  260. abort(403, __('main.not_authorized'));
  261. }
  262.  
  263. /** @var Topic $topic */
  264. $topic = Topic::query()->find($id);
  265.  
  266. if (! $topic) {
  267. abort(404, __('forums.topic_not_exist'));
  268. }
  269.  
  270. $isModer = in_array($user->login, explode(',', (string) $topic->moderators), true);
  271.  
  272. $validator->equal($request->input('_token'), csrf_token(), __('validator.token'))
  273. ->notEmpty($del, __('validator.deletion'))
  274. ->empty($topic->closed, __('forums.topic_closed'))
  275. ->equal($isModer, true, __('forums.posts_deleted_curators'));
  276.  
  277. if ($validator->isValid()) {
  278. // ------ Удаление загруженных файлов -------//
  279. $files = File::query()
  280. ->where('relate_type', Post::$morphName)
  281. ->whereIn('relate_id', $del)
  282. ->get();
  283.  
  284. if ($files->isNotEmpty()) {
  285. foreach ($files as $file) {
  286. $file->delete();
  287. }
  288. }
  289.  
  290. $delPosts = Post::query()->whereIn('id', $del)->delete();
  291.  
  292. $topic->decrement('count_posts', $delPosts);
  293. $topic->forum->decrement('count_posts', $delPosts);
  294.  
  295. setFlash('success', __('main.messages_deleted_success'));
  296. } else {
  297. setFlash('danger', $validator->getErrors());
  298. }
  299.  
  300. return redirect('topics/' . $topic->id . '?page=' . $page);
  301. }
  302.  
  303. /**
  304. * Close topic
  305. *
  306. * @param int $id
  307. * @param Request $request
  308. * @param Validator $validator
  309. *
  310. * @return RedirectResponse
  311. */
  312. public function close(int $id, Request $request, Validator $validator): RedirectResponse
  313. {
  314. if (! $user = getUser()) {
  315. abort(403, __('main.not_authorized'));
  316. }
  317.  
  318. /** @var Topic $topic */
  319. $topic = Topic::query()->find($id);
  320.  
  321. $validator->equal($request->input('_token'), csrf_token(), __('validator.token'))
  322. ->gte($user->point, setting('editforumpoint'), __('forums.topic_edited_points', ['point' => plural(setting('editforumpoint'), setting('scorename'))]))
  323. ->notEmpty($topic, __('forums.topic_not_exist'))
  324. ->equal($topic->user_id, $user->id, __('forums.topic_not_author'))
  325. ->empty($topic->closed, __('forums.topic_closed'));
  326.  
  327. if ($validator->isValid()) {
  328. $topic->update([
  329. 'closed' => 1,
  330. 'close_user_id' => getUser('id'),
  331. ]);
  332.  
  333. if ($topic->vote) {
  334. $topic->vote->update(['closed' => 1]);
  335. $topic->vote->pollings()->delete();
  336. }
  337.  
  338. setFlash('success', __('forums.topic_success_closed'));
  339. } else {
  340. setFlash('danger', $validator->getErrors());
  341. }
  342.  
  343. return redirect('topics/' . $topic->id);
  344. }
  345.  
  346. /**
  347. * Open topic
  348. *
  349. * @param int $id
  350. * @param Request $request
  351. * @param Validator $validator
  352. *
  353. * @return RedirectResponse
  354. */
  355. public function open(int $id, Request $request, Validator $validator): RedirectResponse
  356. {
  357. if (! $user = getUser()) {
  358. abort(403, __('main.not_authorized'));
  359. }
  360.  
  361. /** @var Topic $topic */
  362. $topic = Topic::query()->find($id);
  363.  
  364. $validator->equal($request->input('_token'), csrf_token(), __('validator.token'))
  365. ->notEmpty($topic, __('forums.topic_not_exist'))
  366. ->equal($topic->user_id, $user->id, __('forums.topic_not_author'))
  367. ->equal($topic->close_user_id, $user->id, __('forums.topic_opened_author'))
  368. ->notEmpty($topic->closed, __('forums.topic_already_open'));
  369.  
  370. if ($validator->isValid()) {
  371. $topic->update([
  372. 'closed' => 0,
  373. 'close_user_id' => null,
  374. ]);
  375.  
  376. if ($topic->vote) {
  377. $topic->vote->update(['closed' => 0]);
  378. }
  379.  
  380. setFlash('success', __('forums.topic_success_opened'));
  381. } else {
  382. setFlash('danger', $validator->getErrors());
  383. }
  384.  
  385. return redirect('topics/' . $topic->id);
  386. }
  387.  
  388. /**
  389. * Topic editing
  390. *
  391. * @param int $id
  392. * @param Request $request
  393. * @param Validator $validator
  394. *
  395. * @return View|RedirectResponse
  396. */
  397. public function edit(int $id, Request $request, Validator $validator)
  398. {
  399. if (! $user = getUser()) {
  400. abort(403, __('main.not_authorized'));
  401. }
  402.  
  403. if ($user->point < setting('editforumpoint')) {
  404. abort(200, __('forums.topic_edited_points', ['point' => plural(setting('editforumpoint'), setting('scorename'))]));
  405. }
  406.  
  407. /** @var Topic $topic */
  408. $topic = Topic::query()->find($id);
  409.  
  410. if (! $topic) {
  411. abort(404, __('forums.topic_not_exist'));
  412. }
  413.  
  414. if ($topic->user_id !== $user->id) {
  415. abort(200, __('forums.topic_not_author'));
  416. }
  417.  
  418. if ($topic->closed) {
  419. abort(200, __('forums.topic_closed'));
  420. }
  421.  
  422. $post = Post::query()->where('topic_id', $topic->id)
  423. ->orderBy('id')
  424. ->first();
  425.  
  426. /** @var Vote $vote */
  427. $vote = Vote::query()->where('topic_id', $id)->first();
  428.  
  429. if ($request->isMethod('post')) {
  430. $title = $request->input('title');
  431. $msg = $request->input('msg');
  432. $question = $request->input('question');
  433. $answers = (array) $request->input('answers');
  434.  
  435. $validator->equal($request->input('_token'), csrf_token(), __('validator.token'))
  436. ->length($title, 3, 50, ['title' => __('validator.text')]);
  437.  
  438. if ($post) {
  439. $validator->length($msg, 5, setting('forumtextlength'), ['msg' => __('validator.text')]);
  440. }
  441.  
  442. if ($vote) {
  443. $validator->length($question, 5, 100, ['question' => __('validator.text')]);
  444.  
  445. if ($answers) {
  446. $validator->empty($vote->count, ['question' => __('votes.answer_changed_impossible')]);
  447.  
  448. $answers = array_unique(array_diff($answers, ['']));
  449.  
  450. foreach ($answers as $answer) {
  451. if (utfStrlen($answer) > 50) {
  452. $validator->addError(['answers' => __('votes.answer_wrong_length')]);
  453. break;
  454. }
  455. }
  456.  
  457. $validator->between(count($answers), 2, 10, ['answers' => __('votes.answer_not_enough')]);
  458. }
  459. }
  460.  
  461. if ($validator->isValid()) {
  462. $title = antimat($title);
  463. $msg = antimat($msg);
  464.  
  465. $topic->update(['title' => $title]);
  466.  
  467. if ($post) {
  468. $post->update([
  469. 'text' => $msg,
  470. 'edit_user_id' => $user->id,
  471. 'updated_at' => SITETIME,
  472. ]);
  473. }
  474.  
  475. if ($vote) {
  476. $vote->update([
  477. 'title' => $question,
  478. ]);
  479.  
  480. if ($answers) {
  481. $countAnswers = $vote->answers()->count();
  482.  
  483. foreach ($answers as $answerId => $answer) {
  484. /** @var VoteAnswer $ans */
  485. $ans = $vote->answers()->firstOrNew(['id' => $answerId]);
  486.  
  487. if ($ans->exists) {
  488. $ans->update(['answer' => $answer]);
  489. } elseif ($countAnswers < 10) {
  490. $ans->fill(['answer' => $answer])->save();
  491. $countAnswers++;
  492. }
  493. }
  494. }
  495. }
  496.  
  497. setFlash('success', __('forums.topic_success_changed'));
  498.  
  499. return redirect('topics/' . $topic->id);
  500. }
  501.  
  502. setInput($request->all());
  503. setFlash('danger', $validator->getErrors());
  504. }
  505.  
  506. if ($vote) {
  507. $vote->getAnswers = $vote->answers->pluck('answer', 'id')->all();
  508. }
  509.  
  510. return view('forums/topic_edit', compact('post', 'topic', 'vote'));
  511. }
  512.  
  513. /**
  514. * Post editing
  515. *
  516. * @param int $id
  517. * @param Request $request
  518. * @param Validator $validator
  519. *
  520. * @return View|RedirectResponse
  521. */
  522. public function editPost(int $id, Request $request, Validator $validator)
  523. {
  524. $page = int($request->input('page'));
  525.  
  526. if (! $user = getUser()) {
  527. abort(403, __('main.not_authorized'));
  528. }
  529.  
  530. /** @var Post $post */
  531. $post = Post::query()
  532. ->select('posts.*', 'moderators', 'closed')
  533. ->leftJoin('topics', 'posts.topic_id', 'topics.id')
  534. ->where('posts.id', $id)
  535. ->first();
  536.  
  537. if (! $post) {
  538. abort(404, __('forums.post_not_exist'));
  539. }
  540.  
  541. if ($post->closed) {
  542. abort(200, __('forums.topic_closed'));
  543. }
  544.  
  545. $isModer = in_array($user->login, explode(',', (string) $post->moderators), true);
  546.  
  547. if (! $isModer && $post->user_id !== $user->id) {
  548. abort(200, __('forums.posts_edited_curators'));
  549. }
  550.  
  551. if (! $isModer && $post->created_at + 600 < SITETIME) {
  552. abort(200, __('main.editing_impossible'));
  553. }
  554.  
  555. if ($request->isMethod('post')) {
  556. $msg = $request->input('msg');
  557.  
  558. $validator->equal($request->input('_token'), csrf_token(), __('validator.token'))
  559. ->length($msg, 5, setting('forumtextlength'), ['msg' => __('validator.text')]);
  560.  
  561. if ($validator->isValid()) {
  562. $post->update([
  563. 'text' => antimat($msg),
  564. 'edit_user_id' => $user->id,
  565. 'updated_at' => SITETIME,
  566. ]);
  567.  
  568. setFlash('success', __('main.message_edited_success'));
  569.  
  570. return redirect('topics/' . $post->topic_id . '?page=' . $page);
  571. }
  572.  
  573. setInput($request->all());
  574. setFlash('danger', $validator->getErrors());
  575. }
  576.  
  577. return view('forums/topic_edit_post', compact('post', 'page'));
  578. }
  579.  
  580. /**
  581. * Voting
  582. *
  583. * @param int $id
  584. * @param Request $request
  585. * @param Validator $validator
  586. *
  587. * @return RedirectResponse
  588. */
  589. public function vote(int $id, Request $request, Validator $validator): RedirectResponse
  590. {
  591. if (! $user = getUser()) {
  592. abort(403, __('main.not_authorized'));
  593. }
  594.  
  595. /** @var Vote $vote */
  596. $vote = Vote::query()->where('topic_id', $id)->first();
  597.  
  598. if (! $vote) {
  599. abort(404, __('votes.voting_not_found'));
  600. }
  601.  
  602. $poll = int($request->input('poll'));
  603. $page = int($request->input('page'));
  604.  
  605. $validator->equal($request->input('_token'), csrf_token(), __('validator.token'))
  606. ->notEmpty($poll, __('votes.answer_not_chosen'))
  607. ->empty($vote->closed, __('votes.voting_closed'));
  608.  
  609. if ($validator->isValid()) {
  610. $polling = $vote->pollings()
  611. ->where('user_id', $user->id)
  612. ->first();
  613. $validator->empty($polling, __('votes.voting_passed'));
  614. }
  615.  
  616. if ($validator->isValid()) {
  617. /** @var VoteAnswer $answer */
  618. $answer = $vote->answers()
  619. ->where('id', $poll)
  620. ->where('vote_id', $vote->id)
  621. ->first();
  622. $validator->notEmpty($answer, __('votes.answer_not_found'));
  623. }
  624.  
  625. if ($validator->isValid()) {
  626. $vote->increment('count');
  627. $answer->increment('result');
  628.  
  629. Polling::query()->create([
  630. 'relate_type' => Vote::$morphName,
  631. 'relate_id' => $vote->id,
  632. 'user_id' => $user->id,
  633. 'vote' => $answer->answer,
  634. 'created_at' => SITETIME,
  635. ]);
  636.  
  637. setFlash('success', __('votes.voting_success'));
  638. } else {
  639. setFlash('danger', $validator->getErrors());
  640. }
  641.  
  642. return redirect('topics/' . $vote->topic_id . '?page=' . $page);
  643. }
  644.  
  645. /**
  646. * Print topic
  647. *
  648. * @param int $id
  649. *
  650. * @return View
  651. */
  652. public function print(int $id): View
  653. {
  654. /** @var Topic $topic */
  655. $topic = Topic::query()->find($id);
  656.  
  657. if (! $topic) {
  658. abort(404, __('forums.topic_not_exist'));
  659. }
  660.  
  661. $posts = Post::query()
  662. ->where('topic_id', $topic->id)
  663. ->with('user')
  664. ->orderBy('created_at')
  665. ->get();
  666.  
  667. $description = $posts->first() ? truncateDescription(bbCode($posts->first()->text, false)) : $topic->title;
  668.  
  669. return view('forums/print', compact('topic', 'posts', 'description'));
  670. }
  671.  
  672. /**
  673. * Forward to message
  674. *
  675. * @param int $id
  676. * @param int $pid
  677. *
  678. * @return RedirectResponse
  679. */
  680. public function viewpost(int $id, int $pid): RedirectResponse
  681. {
  682. $countTopics = Post::query()
  683. ->where('id', '<=', $pid)
  684. ->where('topic_id', $id)
  685. ->count();
  686.  
  687. if (! $countTopics) {
  688. abort(404, __('forums.topic_not_exist'));
  689. }
  690.  
  691. $end = ceil($countTopics / setting('forumpost'));
  692.  
  693. return redirect('topics/' . $id . '?page=' . $end . '#post_' . $pid);
  694. }
  695.  
  696. /**
  697. * Forward to the last message
  698. *
  699. * @param int $id
  700. *
  701. * @return RedirectResponse
  702. */
  703. public function end(int $id): RedirectResponse
  704. {
  705. /** @var Topic $topic */
  706. $topic = Topic::query()->find($id);
  707.  
  708. if (! $topic) {
  709. abort(404, __('forums.topic_not_exist'));
  710. }
  711.  
  712. $end = ceil($topic->count_posts / setting('forumpost'));
  713.  
  714. return redirect('topics/' . $topic->id . '?page=' . $end);
  715. }
  716. }