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

Размер файла: 22.83Kb
  1. <?php
  2.  
  3. declare(strict_types=1);
  4.  
  5. namespace App\Http\Controllers;
  6.  
  7. use App\Classes\Validator;
  8. use App\Models\Article;
  9. use App\Models\Blog;
  10. use App\Models\Comment;
  11. use App\Models\File;
  12. use App\Models\Flood;
  13. use App\Models\Reader;
  14. use Illuminate\Database\Query\JoinClause;
  15. use Illuminate\Http\RedirectResponse;
  16. use Illuminate\Http\Request;
  17. use Illuminate\Support\Facades\Cache;
  18. use Illuminate\View\View;
  19.  
  20. class ArticleController extends Controller
  21. {
  22. /**
  23. * Главная страница
  24. *
  25. * @return View
  26. */
  27. public function index(): View
  28. {
  29. $categories = Blog::query()
  30. ->where('parent_id', 0)
  31. ->orderBy('sort')
  32. ->with('children', 'new', 'children.new')
  33. ->get();
  34.  
  35. if ($categories->isEmpty()) {
  36. abort(200, __('blogs.categories_not_created'));
  37. }
  38.  
  39. return view('blogs/index', compact('categories'));
  40. }
  41.  
  42. /**
  43. * Список статей
  44. *
  45. * @param int $id
  46. *
  47. * @return View
  48. */
  49. public function blog(int $id): View
  50. {
  51. $category = Blog::query()->with('parent')->find($id);
  52.  
  53. if (! $category) {
  54. abort(404, __('blogs.category_not_exist'));
  55. }
  56.  
  57. $articles = Article::query()
  58. ->select('articles.*', 'pollings.vote')
  59. ->leftJoin('pollings', static function (JoinClause $join) {
  60. $join->on('articles.id', 'pollings.relate_id')
  61. ->where('pollings.relate_type', Article::$morphName)
  62. ->where('pollings.user_id', getUser('id'));
  63. })
  64. ->where('category_id', $id)
  65. ->orderByDesc('created_at')
  66. ->with('user')
  67. ->paginate(setting('blogpost'));
  68.  
  69. return view('blogs/blog', compact('articles', 'category'));
  70. }
  71.  
  72. /**
  73. * Просмотр статьи
  74. *
  75. * @param int $id
  76. *
  77. * @return View
  78. */
  79. public function view(int $id): View
  80. {
  81. /** @var Article $article */
  82. $article = Article::query()
  83. ->select('articles.*', 'pollings.vote')
  84. ->where('articles.id', $id)
  85. ->leftJoin('pollings', static function (JoinClause $join) {
  86. $join->on('articles.id', 'pollings.relate_id')
  87. ->where('pollings.relate_type', Article::$morphName)
  88. ->where('pollings.user_id', getUser('id'));
  89. })
  90. ->with('category.parent')
  91. ->first();
  92.  
  93. if (! $article) {
  94. abort(404, __('blogs.article_not_exist'));
  95. }
  96.  
  97. Reader::countingStat($article);
  98.  
  99. $tagsList = preg_split('/[\s]*[,][\s]*/', $article->tags);
  100.  
  101. $tags = [];
  102. foreach ($tagsList as $value) {
  103. $tags[] = '<a href="/blogs/tags/' . check(urlencode($value)) . '">' . check($value) . '</a>';
  104. }
  105. $tags = implode(', ', $tags);
  106.  
  107. return view('blogs/view', compact('article', 'tags'));
  108. }
  109.  
  110. /**
  111. * Редактирование статьи
  112. *
  113. * @param int $id
  114. * @param Request $request
  115. * @param Validator $validator
  116. *
  117. * @return View|RedirectResponse
  118. */
  119. public function edit(int $id, Request $request, Validator $validator): View|RedirectResponse
  120. {
  121. if (! $user = getUser()) {
  122. abort(403, __('main.not_authorized'));
  123. }
  124.  
  125. /** @var Article $article */
  126. $article = Article::query()->find($id);
  127.  
  128. if (! $article) {
  129. abort(404, __('blogs.article_not_exist'));
  130. }
  131.  
  132. if ($user->id !== $article->user_id) {
  133. abort(200, __('main.article_not_author'));
  134. }
  135.  
  136. if ($request->isMethod('post')) {
  137. $cid = int($request->input('cid'));
  138. $title = $request->input('title');
  139. $text = $request->input('text');
  140. $tags = $request->input('tags');
  141.  
  142. /** @var Blog $category */
  143. $category = Blog::query()->find($cid);
  144.  
  145. $validator
  146. ->equal($request->input('_token'), csrf_token(), __('validator.token'))
  147. ->length($title, 3, 50, ['title' => __('validator.text')])
  148. ->length($text, 100, setting('maxblogpost'), ['text' => __('validator.text')])
  149. ->length($tags, 2, 50, ['tags' => __('blogs.article_error_tags')])
  150. ->notEmpty($category, ['cid' => __('blogs.category_not_exist')]);
  151.  
  152. if ($category) {
  153. $validator->empty($category->closed, ['cid' => __('blogs.category_closed')]);
  154. }
  155.  
  156. if ($validator->isValid()) {
  157. // Обновление счетчиков
  158. if ($article->category_id !== $category->id) {
  159. $category->increment('count_articles');
  160. Blog::query()->where('id', $article->category_id)->decrement('count_articles');
  161. }
  162.  
  163. $article->update([
  164. 'category_id' => $category->id,
  165. 'title' => $title,
  166. 'text' => $text,
  167. 'tags' => $tags,
  168. ]);
  169.  
  170. clearCache(['statArticles', 'recentArticles', 'ArticleFeed']);
  171. setFlash('success', __('blogs.article_success_edited'));
  172.  
  173. return redirect('articles/' . $article->id);
  174. }
  175.  
  176. setInput($request->all());
  177. setFlash('danger', $validator->getErrors());
  178. }
  179.  
  180. $categories = $article->category->getChildren();
  181.  
  182. return view('blogs/edit', compact('article', 'categories'));
  183. }
  184.  
  185. /**
  186. * Просмотр по категориям
  187. *
  188. * @return View
  189. */
  190. public function authors(): View
  191. {
  192. $articles = Article::query()
  193. ->select('user_id', 'login')
  194. ->selectRaw('count(*) as cnt, sum(count_comments) as count_comments')
  195. ->join('users', 'articles.user_id', 'users.id')
  196. ->groupBy('user_id')
  197. ->orderByDesc('cnt')
  198. ->paginate(setting('bloggroup'));
  199.  
  200. return view('blogs/authors', compact('articles'));
  201. }
  202.  
  203. /**
  204. * Создание статьи
  205. *
  206. * @param Request $request
  207. * @param Validator $validator
  208. * @param Flood $flood
  209. *
  210. * @return View|RedirectResponse
  211. */
  212. public function create(Request $request, Validator $validator, Flood $flood): View|RedirectResponse
  213. {
  214. $cid = int($request->input('cid'));
  215.  
  216. if (! isAdmin() && ! setting('blog_create')) {
  217. abort(200, __('main.articles_closed'));
  218. }
  219.  
  220. if (! $user = getUser()) {
  221. abort(403, __('main.not_authorized'));
  222. }
  223.  
  224. $categories = (new Blog())->getChildren();
  225.  
  226. if ($categories->isEmpty()) {
  227. abort(404, __('blogs.categories_not_created'));
  228. }
  229.  
  230. if ($request->isMethod('post')) {
  231. $title = $request->input('title');
  232. $text = $request->input('text');
  233. $tags = $request->input('tags');
  234.  
  235. /** @var Blog $category */
  236. $category = Blog::query()->find($cid);
  237.  
  238. $validator
  239. ->equal($request->input('_token'), csrf_token(), __('validator.token'))
  240. ->length($title, 3, 50, ['title' => __('validator.text')])
  241. ->length($text, 100, setting('maxblogpost'), ['text' => __('validator.text')])
  242. ->length($tags, 2, 50, ['tags' => __('blogs.article_error_tags')])
  243. ->false($flood->isFlood(), ['msg' => __('validator.flood', ['sec' => $flood->getPeriod()])])
  244. ->notEmpty($category, ['cid' => __('blogs.category_not_exist')]);
  245.  
  246. if ($category) {
  247. $validator->empty($category->closed, ['cid' => __('blogs.category_closed')]);
  248. }
  249.  
  250. if ($validator->isValid()) {
  251. $text = antimat($text);
  252.  
  253. /** @var Article $article */
  254. $article = Article::query()->create([
  255. 'category_id' => $cid,
  256. 'user_id' => $user->id,
  257. 'title' => $title,
  258. 'text' => $text,
  259. 'tags' => $tags,
  260. 'created_at' => SITETIME,
  261. ]);
  262.  
  263. $category->increment('count_articles');
  264.  
  265. $user->increment('point', 5);
  266. $user->increment('money', 100);
  267.  
  268. File::query()
  269. ->where('relate_type', Article::$morphName)
  270. ->where('relate_id', 0)
  271. ->where('user_id', $user->id)
  272. ->update(['relate_id' => $article->id]);
  273.  
  274. clearCache(['statArticles', 'recentArticles', 'ArticleFeed']);
  275. $flood->saveState();
  276.  
  277. setFlash('success', __('blogs.article_success_created'));
  278.  
  279. return redirect('articles/' . $article->id);
  280. }
  281.  
  282. setInput($request->all());
  283. setFlash('danger', $validator->getErrors());
  284. }
  285.  
  286. $files = File::query()
  287. ->where('relate_type', Article::$morphName)
  288. ->where('relate_id', 0)
  289. ->where('user_id', $user->id)
  290. ->get();
  291.  
  292. return view('blogs/create', compact('categories', 'cid', 'files'));
  293. }
  294.  
  295. /**
  296. * Комментарии
  297. *
  298. * @param int $id
  299. * @param Request $request
  300. * @param Validator $validator
  301. * @param Flood $flood
  302. *
  303. * @return View|RedirectResponse
  304. */
  305. public function comments(int $id, Request $request, Validator $validator, Flood $flood): View|RedirectResponse
  306. {
  307. /** @var Article $article */
  308. $article = Article::query()->find($id);
  309.  
  310. if (! $article) {
  311. abort(404, __('blogs.article_not_exist'));
  312. }
  313.  
  314. if ($request->isMethod('post')) {
  315. $user = getUser();
  316. $msg = $request->input('msg');
  317.  
  318. $validator
  319. ->true($user, __('main.not_authorized'))
  320. ->equal($request->input('_token'), csrf_token(), __('validator.token'))
  321. ->length($msg, 5, setting('comment_length'), ['msg' => __('validator.text')])
  322. ->false($flood->isFlood(), ['msg' => __('validator.flood', ['sec' => $flood->getPeriod()])]);
  323.  
  324. if ($validator->isValid()) {
  325. /** @var Comment $comment */
  326. $comment = $article->comments()->create([
  327. 'text' => antimat($msg),
  328. 'user_id' => $user->id,
  329. 'created_at' => SITETIME,
  330. 'ip' => getIp(),
  331. 'brow' => getBrowser(),
  332. ]);
  333.  
  334. $user->increment('allcomments');
  335. $user->increment('point');
  336. $user->increment('money', 5);
  337.  
  338. $article->increment('count_comments');
  339.  
  340. $flood->saveState();
  341. sendNotify($msg, '/articles/comment/' . $article->id . '/' . $comment->id, $article->title);
  342.  
  343. setFlash('success', __('main.comment_added_success'));
  344.  
  345. return redirect('articles/end/' . $article->id);
  346. }
  347.  
  348. setInput($request->all());
  349. setFlash('danger', $validator->getErrors());
  350. }
  351.  
  352. $comments = $article->comments()
  353. ->with('user')
  354. ->orderBy('created_at')
  355. ->paginate(setting('comments_per_page'));
  356.  
  357. return view('blogs/comments', compact('article', 'comments'));
  358. }
  359.  
  360. /**
  361. * Подготовка к редактированию комментария
  362. *
  363. * @param int $id
  364. * @param int $cid
  365. * @param Request $request
  366. * @param Validator $validator
  367. *
  368. * @return View|RedirectResponse
  369. */
  370. public function editComment(int $id, int $cid, Request $request, Validator $validator): View|RedirectResponse
  371. {
  372. $page = int($request->input('page', 1));
  373.  
  374. /** @var Article $article */
  375. $article = Article::query()->find($id);
  376.  
  377. if (! $article) {
  378. abort(404, __('blogs.article_not_exist'));
  379. }
  380.  
  381. if (! $user = getUser()) {
  382. abort(403, __('main.not_authorized'));
  383. }
  384.  
  385. $comment = $article->comments()
  386. ->where('id', $cid)
  387. ->where('user_id', $user->id)
  388. ->first();
  389.  
  390. if (! $comment) {
  391. abort(200, __('main.comment_deleted'));
  392. }
  393.  
  394. if ($comment->created_at + 600 < SITETIME) {
  395. abort(200, __('main.editing_impossible'));
  396. }
  397.  
  398. if ($request->isMethod('post')) {
  399. $msg = $request->input('msg');
  400. $page = int($request->input('page', 1));
  401.  
  402. $validator
  403. ->equal($request->input('_token'), csrf_token(), __('validator.token'))
  404. ->length($msg, 5, setting('comment_length'), ['msg' => __('validator.text')]);
  405.  
  406. if ($validator->isValid()) {
  407. $msg = antimat($msg);
  408.  
  409. $comment->update([
  410. 'text' => $msg,
  411. ]);
  412.  
  413. setFlash('success', __('main.comment_edited_success'));
  414.  
  415. return redirect('articles/comments/' . $article->id . '?page=' . $page);
  416. }
  417.  
  418. setInput($request->all());
  419. setFlash('danger', $validator->getErrors());
  420. }
  421.  
  422. return view('blogs/editcomment', compact('article', 'comment', 'page'));
  423. }
  424.  
  425. /**
  426. * Переадресация на последнюю страницу
  427. *
  428. * @param int $id
  429. *
  430. * @return RedirectResponse
  431. */
  432. public function end(int $id): RedirectResponse
  433. {
  434. /** @var Article $article */
  435. $article = Article::query()->find($id);
  436.  
  437. if (! $article) {
  438. abort(404, __('blogs.article_not_exist'));
  439. }
  440.  
  441. $total = $article->comments()->count();
  442.  
  443. $end = ceil($total / setting('comments_per_page'));
  444.  
  445. return redirect('articles/comments/' . $id . '?page=' . $end);
  446. }
  447.  
  448. /**
  449. * Печать
  450. *
  451. * @param int $id
  452. *
  453. * @return View
  454. */
  455. public function print(int $id): View
  456. {
  457. /** @var Article $article */
  458. $article = Article::query()->find($id);
  459.  
  460. if (! $article) {
  461. abort(404, __('blogs.article_not_exist'));
  462. }
  463.  
  464. return view('blogs/print', compact('article'));
  465. }
  466.  
  467. /**
  468. * RSS всех блогов
  469. *
  470. * @return View
  471. */
  472. public function rss(): View
  473. {
  474. $articles = Article::query()
  475. ->orderByDesc('created_at')
  476. ->with('user')
  477. ->limit(15)
  478. ->get();
  479.  
  480. if ($articles->isEmpty()) {
  481. abort(200, __('blogs.article_not_exist'));
  482. }
  483.  
  484. return view('blogs/rss', compact('articles'));
  485. }
  486.  
  487. /**
  488. * RSS комментариев к блогу
  489. *
  490. * @param int $id
  491. *
  492. * @return View
  493. */
  494. public function rssComments(int $id): View
  495. {
  496. $article = Article::query()->where('id', $id)->with('lastComments')->first();
  497.  
  498. if (! $article) {
  499. abort(404, __('blogs.article_not_exist'));
  500. }
  501.  
  502. return view('blogs/rss_comments', compact('article'));
  503. }
  504.  
  505. /**
  506. * Вывод всех тегов
  507. *
  508. * @return View
  509. */
  510. public function tags(): View
  511. {
  512. $tags = Cache::remember('tagCloud', 3600, static function () {
  513. $allTags = Article::query()
  514. ->select('tags')
  515. ->pluck('tags')
  516. ->all();
  517.  
  518. $stingTag = implode(',', $allTags);
  519. $dumptags = preg_split('/[\s]*[,][\s]*/', $stingTag, -1, PREG_SPLIT_NO_EMPTY);
  520. $allTags = array_count_values(array_map('utfLower', $dumptags));
  521.  
  522. arsort($allTags);
  523. array_splice($allTags, 100);
  524. shuffleAssoc($allTags);
  525.  
  526. return $allTags;
  527. });
  528.  
  529. $max = $tags ? max($tags) : 0;
  530. $min = $tags ? min($tags) : 0;
  531.  
  532. return view('blogs/tags', compact('tags', 'max', 'min'));
  533. }
  534.  
  535. /**
  536. * Поиск по тегам
  537. *
  538. * @param string $tag
  539. * @param Request $request
  540. *
  541. * @return View|RedirectResponse
  542. */
  543. public function searchTag(string $tag, Request $request): View|RedirectResponse
  544. {
  545. $tag = urldecode($tag);
  546.  
  547. if (! isUtf($tag)) {
  548. $tag = winToUtf($tag);
  549. }
  550.  
  551. if (utfStrlen($tag) < 2) {
  552. setFlash('danger', __('blogs.tag_search_rule'));
  553.  
  554. return redirect('blogs/tags');
  555. }
  556.  
  557. if ($request->session()->missing(['findresult', 'blogfind']) ||
  558. $tag !== $request->session()->get('blogfind')
  559. ) {
  560. $result = Article::query()
  561. ->select('id')
  562. ->where('tags', 'like', '%' . $tag . '%')
  563. ->limit(500)
  564. ->pluck('id')
  565. ->all();
  566.  
  567. $request->session()->put('blogfind', $tag);
  568. $request->session()->put('findresult', $result);
  569. }
  570.  
  571. $articles = Article::query()
  572. ->select('articles.*', 'blogs.name')
  573. ->whereIn('articles.id', $request->session()->get('findresult'))
  574. ->join('blogs', 'articles.category_id', 'blogs.id')
  575. ->orderByDesc('created_at')
  576. ->with('user')
  577. ->paginate(setting('blogpost'));
  578.  
  579. return view('blogs/tags_search', compact('articles', 'tag'));
  580. }
  581.  
  582. /**
  583. * Новые статьи
  584. *
  585. * @return View
  586. */
  587. public function newArticles(): View
  588. {
  589. $articles = Article::query()
  590. ->orderByDesc('created_at')
  591. ->with('user', 'category')
  592. ->paginate(setting('blogpost'));
  593.  
  594. return view('blogs/new_articles', compact('articles'));
  595. }
  596.  
  597. /**
  598. * Новые комментарии
  599. *
  600. * @return View
  601. */
  602. public function newComments(): View
  603. {
  604. $comments = Comment::query()
  605. ->select('comments.*', 'title', 'count_comments')
  606. ->where('relate_type', Article::$morphName)
  607. ->leftJoin('articles', 'comments.relate_id', 'articles.id')
  608. ->orderByDesc('comments.created_at')
  609. ->with('user', 'relate')
  610. ->paginate(setting('comments_per_page'));
  611.  
  612. return view('blogs/new_comments', compact('comments'));
  613. }
  614.  
  615. /**
  616. * Статьи пользователя
  617. *
  618. * @param Request $request
  619. *
  620. * @return View
  621. */
  622. public function userArticles(Request $request): View
  623. {
  624. $login = $request->input('user', getUser('login'));
  625. $user = getUserByLogin($login);
  626.  
  627. if (! $user) {
  628. abort(404, __('validator.user'));
  629. }
  630.  
  631. $articles = Article::query()->where('user_id', $user->id)
  632. ->orderByDesc('created_at')
  633. ->with('user')
  634. ->paginate(setting('blogpost'))
  635. ->appends(['user' => $user->login]);
  636.  
  637. return view('blogs/active_articles', compact('articles', 'user'));
  638. }
  639.  
  640. /**
  641. * Комментарии пользователя
  642. *
  643. * @param Request $request
  644. *
  645. * @return View
  646. */
  647. public function userComments(Request $request): View
  648. {
  649. $login = $request->input('user', getUser('login'));
  650. $user = getUserByLogin($login);
  651.  
  652. if (! $user) {
  653. abort(404, __('validator.user'));
  654. }
  655.  
  656. $comments = Comment::query()
  657. ->select('comments.*', 'title', 'count_comments')
  658. ->where('relate_type', Article::$morphName)
  659. ->where('comments.user_id', $user->id)
  660. ->leftJoin('articles', 'comments.relate_id', 'articles.id')
  661. ->orderByDesc('comments.created_at')
  662. ->with('user', 'relate')
  663. ->paginate(setting('comments_per_page'))
  664. ->appends(['user' => $user->login]);
  665.  
  666. return view('blogs/active_comments', compact('comments', 'user'));
  667. }
  668.  
  669. /**
  670. * Переход к сообщению
  671. *
  672. * @param int $id
  673. * @param int $cid
  674. *
  675. * @return RedirectResponse
  676. */
  677. public function viewComment(int $id, int $cid): RedirectResponse
  678. {
  679. /** @var Article $article */
  680. $article = Article::query()->find($id);
  681.  
  682. if (! $article) {
  683. abort(404, __('blogs.article_not_exist'));
  684. }
  685.  
  686. $total = $article->comments()
  687. ->where('id', '<=', $cid)
  688. ->orderBy('created_at')
  689. ->count();
  690.  
  691. $end = ceil($total / setting('comments_per_page'));
  692.  
  693. return redirect('articles/comments/' . $article->id . '?page=' . $end . '#comment_' . $cid);
  694. }
  695.  
  696. /**
  697. * Топ статей
  698. *
  699. * @param Request $request
  700. *
  701. * @return View
  702. */
  703. public function top(Request $request): View
  704. {
  705. $sort = check($request->input('sort', 'visits'));
  706. $order = match ($sort) {
  707. 'rating' => 'rating',
  708. 'comments' => 'count_comments',
  709. default => 'visits',
  710. };
  711.  
  712. $articles = Article::query()
  713. ->select('articles.*', 'blogs.name')
  714. ->leftJoin('blogs', 'articles.category_id', 'blogs.id')
  715. ->orderByDesc($order)
  716. ->with('user')
  717. ->paginate(setting('blogpost'))
  718. ->appends(['sort' => $sort]);
  719.  
  720. return view('blogs/top', compact('articles', 'order'));
  721. }
  722.  
  723. /**
  724. * Поиск
  725. *
  726. * @param Request $request
  727. * @param Validator $validator
  728. *
  729. * @return View|RedirectResponse
  730. */
  731. public function search(Request $request, Validator $validator)
  732. {
  733. $find = check($request->input('find'));
  734. $articles = collect();
  735.  
  736. if ($find) {
  737. $find = rawurldecode(trim(preg_replace('/[^\w\x7F-\xFF\s]/', ' ', $find)));
  738.  
  739. $validator->length($find, 3, 64, ['find' => __('main.request_length')]);
  740. if ($validator->isValid()) {
  741. $articles = Article::query()
  742. ->whereFullText(['title', 'text'], $find . '*', ['mode' => 'boolean'])
  743. ->with('user', 'category')
  744. ->paginate(setting('blogpost'))
  745. ->appends(compact('find'));
  746.  
  747. if ($articles->isEmpty()) {
  748. setInput($request->all());
  749. setFlash('danger', __('main.empty_found'));
  750.  
  751. return redirect('loads/search');
  752. }
  753. } else {
  754. setInput($request->all());
  755. setFlash('danger', $validator->getErrors());
  756. }
  757. }
  758.  
  759. return view('blogs/search', compact('articles', 'find'));
  760. }
  761.  
  762. /**
  763. * Список всех блогов (Для вывода на главную страницу)
  764. *
  765. * @return View
  766. */
  767. public function main(): View
  768. {
  769. $articles = Article::query()
  770. ->orderByDesc('created_at')
  771. ->with('user', 'category.parent')
  772. ->paginate(setting('blogpost'));
  773.  
  774. return view('blogs/main', compact('articles'));
  775. }
  776. }