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

Размер файла: 9.89Kb
  1. <?php
  2.  
  3. declare(strict_types=1);
  4.  
  5. namespace App\Controllers;
  6.  
  7. use App\Models\File;
  8. use App\Models\Story;
  9. use App\Models\StoryTag;
  10. use App\Models\Tag;
  11. use App\Repositories\FileRepository;
  12. use App\Repositories\ReadRepository;
  13. use App\Repositories\StoryRepository;
  14. use App\Services\Session;
  15. use App\Services\Slug;
  16. use App\Services\Str;
  17. use App\Services\Validator;
  18. use App\Services\View;
  19. use Psr\Http\Message\ResponseInterface as Response;
  20. use Psr\Http\Message\ServerRequestInterface as Request;
  21.  
  22. /**
  23. * StoryController
  24. */
  25. class StoryController extends Controller
  26. {
  27. public function __construct(
  28. protected View $view,
  29. protected Session $session,
  30. protected Validator $validator,
  31. protected FileRepository $fileRepository,
  32. protected StoryRepository $storyRepository,
  33. protected ReadRepository $readRepository,
  34. ) {}
  35.  
  36. /**
  37. * Index
  38. *
  39. * @param Response $response
  40. *
  41. * @return Response
  42. */
  43. public function index(Response $response): Response
  44. {
  45. $stories = $this->storyRepository->getStories(setting('story.per_page'));
  46.  
  47. return $this->view->render(
  48. $response,
  49. 'stories/index',
  50. compact('stories')
  51. );
  52. }
  53.  
  54. /**
  55. * View
  56. *
  57. * @param string $slug
  58. * @param Request $request
  59. * @param Response $response
  60. *
  61. * @return Response
  62. */
  63. public function view(string $slug, Request $request, Response $response): Response
  64. {
  65. $story = $this->storyRepository->getBySlug($slug);
  66. if (! $story) {
  67. abort(404, 'Статья не найдена!');
  68. }
  69.  
  70. // Count reads
  71. $this->readRepository->createRead($story, $request->getAttribute('ip'));
  72.  
  73. $files = $this->fileRepository->getFilesByStoryId($story->id);
  74.  
  75. return $this->view->render(
  76. $response,
  77. 'stories/view',
  78. compact('story', 'files')
  79. );
  80. }
  81.  
  82. /**
  83. * Create
  84. *
  85. * @param Response $response
  86. *
  87. * @return Response
  88. */
  89. public function create(Response $response): Response
  90. {
  91. if (! setting('story.allow_posting') && ! isAdmin()) {
  92. abort(403, 'Публикация статей запрещена администратором!');
  93. }
  94.  
  95. $user = getUser();
  96. $files = $this->fileRepository->getFiles($user->id, 0);
  97.  
  98. return $this->view->render(
  99. $response,
  100. 'stories/create',
  101. compact('files')
  102. );
  103. }
  104.  
  105. /**
  106. * Store
  107. *
  108. * @param Request $request
  109. * @param Response $response
  110. * @param Slug $slug
  111. *
  112. * @return Response
  113. */
  114. public function store(
  115. Request $request,
  116. Response $response,
  117. Slug $slug,
  118. ): Response {
  119. $user = getUser();
  120. $input = (array) $request->getParsedBody();
  121. $tags = array_map('sanitize', $input['tags'] ?? []);
  122.  
  123. $this->validator
  124. ->required(['csrf', 'title', 'text', 'tags'])
  125. ->same('csrf', $this->session->get('csrf'), 'Неверный идентификатор сессии, повторите действие!')
  126. ->length('title', setting('story.title_min_length'), setting('story.title_max_length'))
  127. ->length('text', setting('story.text_min_length'), setting('story.text_max_length'))
  128. ->custom(count($tags) <= setting('story.tags_max'), ['tags' => 'Превышено максимальное количество тегов'])
  129. ->boolean('locked');
  130.  
  131. foreach ($tags as $tag) {
  132. $this->validator->custom(
  133. Str::length($tag) >= setting('story.tags_min_length') && Str::length($tag) <= setting('story.tags_max_length'),
  134. ['tags' => sprintf('Длина тегов должна быть от %d до %d символов!', setting('story.tags_min_length'), setting('story.tags_max_length'))]
  135. );
  136. }
  137.  
  138. if ($this->validator->isValid($input)) {
  139. $slugify = $slug->slugify($input['title']);
  140.  
  141. $story = Story::query()->create([
  142. 'user_id' => $user->id,
  143. 'title' => sanitize($input['title']),
  144. 'slug' => $slugify,
  145. 'text' => sanitize($input['text']),
  146. 'rating' => 0,
  147. 'reads' => 0,
  148. 'locked' => isAdmin() ? $input['locked'] ?? 0 : 0,
  149. 'created_at' => time(),
  150. ]);
  151.  
  152. foreach ($tags as $value) {
  153. Tag::query()->create([
  154. 'story_id' => $story->id,
  155. 'tag' => Str::lower($value),
  156. ]);
  157. }
  158.  
  159. File::query()
  160. ->where('story_id', 0)
  161. ->where('user_id', $user->id)
  162. ->update(['story_id' => $story->id]);
  163.  
  164. $this->session->set('flash', ['success' => 'Статья успешно добавлена!']);
  165.  
  166. return $this->redirect($response, $story->getLink());
  167. }
  168.  
  169. $this->session->set('flash', ['errors' => $this->validator->getErrors(), 'old' => $input]);
  170.  
  171. return $this->redirect($response, '/create');
  172. }
  173.  
  174. /**
  175. * Edit
  176. *
  177. * @param int $id
  178. * @param Response $response
  179. *
  180. * @return Response
  181. */
  182. public function edit(int $id, Response $response): Response
  183. {
  184. $user = getUser();
  185.  
  186. $story = $this->storyRepository->getById($id);
  187. if (! $story) {
  188. abort(404, 'Статья не найдена!');
  189. }
  190.  
  191. if ($story->user_id !== $user->id && ! isAdmin()) {
  192. abort(403, 'Вы не являетесь автором данной записи!');
  193. }
  194.  
  195. $files = $this->fileRepository->getFilesByStoryId($story->id);
  196.  
  197. return $this->view->render(
  198. $response,
  199. 'stories/edit',
  200. compact('story', 'files')
  201. );
  202. }
  203.  
  204. /**
  205. * Update
  206. *
  207. * @param int $id
  208. * @param Request $request
  209. * @param Response $response
  210. * @param Slug $slug
  211. *
  212. * @return Response
  213. */
  214. public function update(
  215. int $id,
  216. Request $request,
  217. Response $response,
  218. Slug $slug,
  219. ): Response
  220. {
  221. $user = getUser();
  222.  
  223. $story = $this->storyRepository->getById($id);
  224. if (! $story) {
  225. abort(404, 'Статья не найдена!');
  226. }
  227.  
  228. if ($story->user_id !== $user->id && ! isAdmin()) {
  229. abort(403, 'Вы не являетесь автором данной записи!');
  230. }
  231.  
  232. $input = (array) $request->getParsedBody();
  233. $tags = array_map('sanitize', $input['tags'] ?? []);
  234.  
  235. $this->validator
  236. ->required(['csrf', 'title', 'text', 'tags'])
  237. ->same('csrf', $this->session->get('csrf'), 'Неверный идентификатор сессии, повторите действие!')
  238. ->length('title', setting('story.title_min_length'), setting('story.title_max_length'))
  239. ->length('text', setting('story.text_min_length'), setting('story.text_max_length'))
  240. ->custom(count($tags) <= setting('story.tags_max'), ['tags' => 'Превышено максимальное количество тегов'])
  241. ->boolean('locked');
  242.  
  243. foreach ($tags as $tag) {
  244. $this->validator->custom(
  245. Str::length($tag) >= setting('story.tags_min_length') && Str::length($tag) <= setting('story.tags_max_length'),
  246. ['tags' => sprintf('Длина тегов должна быть от %d до %d символов!', setting('story.tags_min_length'), setting('story.tags_max_length'))]
  247. );
  248. }
  249.  
  250. Tag::query()->where('story_id', $story->id)->delete();
  251.  
  252. foreach ($tags as $value) {
  253. Tag::query()->create([
  254. 'story_id' => $story->id,
  255. 'tag' => Str::lower($value),
  256. ]);
  257. }
  258.  
  259. if ($this->validator->isValid($input)) {
  260. $slugify = $slug->slugify($input['title']);
  261.  
  262. $story->update([
  263. 'title' => sanitize($input['title']),
  264. 'slug' => $slugify,
  265. 'text' => sanitize($input['text']),
  266. 'locked' => isAdmin() ? $input['locked'] ?? $story->locked : $story->locked,
  267. ]);
  268.  
  269. $this->session->set('flash', ['success' => 'Статья успешно изменена!']);
  270.  
  271. return $this->redirect($response, '/' . $slugify . '-' . $id);
  272. }
  273.  
  274. $this->session->set('flash', ['errors' => $this->validator->getErrors(), 'old' => $input]);
  275.  
  276. return $this->redirect($response, '/' . $id . '/edit');
  277. }
  278.  
  279. /**
  280. * Destroy
  281. *
  282. * @param int $id
  283. * @param Request $request
  284. * @param Response $response
  285. *
  286. * @return Response
  287. */
  288. public function destroy(int $id, Request $request, Response $response): Response
  289. {
  290. $user = getUser();
  291.  
  292. $story = $this->storyRepository->getById($id);
  293. if (! $story) {
  294. abort(404, 'Статья не найдена');
  295. }
  296.  
  297. if ($story->user_id !== $user->id && ! isAdmin()) {
  298. abort(403, 'Вы не являетесь автором данной записи!');
  299. }
  300.  
  301. $input = (array) $request->getParsedBody();
  302.  
  303. $this->validator
  304. ->required('csrf')
  305. ->same('csrf', $this->session->get('csrf'), 'Неверный идентификатор сессии, повторите действие!');
  306.  
  307. if ($this->validator->isValid($input)) {
  308. $story->delete();
  309.  
  310. $this->session->set('flash', ['success' => 'Статья успешно удалена!']);
  311. } else {
  312. $this->session->set('flash', ['errors' => $this->validator->getErrors()]);
  313. }
  314.  
  315. return $this->redirect($response, '/');
  316. }
  317. }