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

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