<?php
declare(strict_types=1);
namespace App\Controllers;
use App\Models\File;
use App\Models\Story;
use App\Models\StoryTag;
use App\Models\Tag;
use App\Repositories\FileRepository;
use App\Repositories\ReadRepository;
use App\Repositories\StoryRepository;
use App\Services\Session;
use App\Services\Slug;
use App\Services\Str;
use App\Services\Validator;
use App\Services\View;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
/**
* StoryController
*/
class StoryController extends Controller
{
public function __construct(
protected View $view,
protected Session $session,
protected Validator $validator,
protected FileRepository $fileRepository,
protected StoryRepository $storyRepository,
protected ReadRepository $readRepository,
) {}
/**
* Index
*
* @param Response $response
*
* @return Response
*/
public function index(Response $response): Response
{
$stories = $this->storyRepository->getStories(setting('story.per_page'));
return $this->view->render(
$response,
'stories/index',
compact('stories')
);
}
/**
* View
*
* @param string $slug
* @param Request $request
* @param Response $response
*
* @return Response
*/
public function view(string $slug, Request $request, Response $response): Response
{
$story = $this->storyRepository->getBySlug($slug);
if (! $story) {
abort(404, 'Статья не найдена!');
}
// Count reads
$this->readRepository->createRead($story, $request->getAttribute('ip'));
$files = $this->fileRepository->getFilesByStoryId($story->id);
return $this->view->render(
$response,
'stories/view',
compact('story', 'files')
);
}
/**
* Create
*
* @param Response $response
*
* @return Response
*/
public function create(Response $response): Response
{
if (! setting('story.allow_posting') && ! isAdmin()) {
abort(403, 'Публикация статей запрещена администратором!');
}
$user = getUser();
$files = $this->fileRepository->getFiles($user->id, 0);
return $this->view->render(
$response,
'stories/create',
compact('files')
);
}
/**
* Store
*
* @param Request $request
* @param Response $response
* @param Slug $slug
*
* @return Response
*/
public function store(
Request $request,
Response $response,
Slug $slug,
): Response {
$user = getUser();
$input = (array) $request->getParsedBody();
$tags = array_map('sanitize', $input['tags'] ?? []);
$this->validator
->required(['csrf', 'title', 'text', 'tags'])
->same('csrf', $this->session->get('csrf'), 'Неверный идентификатор сессии, повторите действие!')
->length('title', setting('story.title_min_length'), setting('story.title_max_length'))
->length('text', setting('story.text_min_length'), setting('story.text_max_length'))
->custom(count($tags) <= setting('story.tags_max'), ['tags' => 'Превышено максимальное количество тегов'])
->boolean('locked');
foreach ($tags as $tag) {
$this->validator->custom(
Str::length($tag) >= setting('story.tags_min_length') && Str::length($tag) <= setting('story.tags_max_length'),
['tags' => sprintf('Длина тегов должна быть от %d до %d символов!', setting('story.tags_min_length'), setting('story.tags_max_length'))]
);
}
if ($this->validator->isValid($input)) {
$slugify = $slug->slugify($input['title']);
$story = Story::query()->create([
'user_id' => $user->id,
'title' => sanitize($input['title']),
'slug' => $slugify,
'text' => sanitize($input['text']),
'rating' => 0,
'reads' => 0,
'locked' => isAdmin() ? $input['locked'] ?? 0 : 0,
'created_at' => time(),
]);
foreach ($tags as $value) {
Tag::query()->create([
'story_id' => $story->id,
'tag' => Str::lower($value),
]);
}
File::query()
->where('story_id', 0)
->where('user_id', $user->id)
->update(['story_id' => $story->id]);
$this->session->set('flash', ['success' => 'Статья успешно добавлена!']);
return $this->redirect($response, $story->getLink());
}
$this->session->set('flash', ['errors' => $this->validator->getErrors(), 'old' => $input]);
return $this->redirect($response, '/create');
}
/**
* Edit
*
* @param int $id
* @param Response $response
*
* @return Response
*/
public function edit(int $id, Response $response): Response
{
$user = getUser();
$story = $this->storyRepository->getById($id);
if (! $story) {
abort(404, 'Статья не найдена!');
}
if ($story->user_id !== $user->id && ! isAdmin()) {
abort(403, 'Вы не являетесь автором данной записи!');
}
$files = $this->fileRepository->getFilesByStoryId($story->id);
return $this->view->render(
$response,
'stories/edit',
compact('story', 'files')
);
}
/**
* Update
*
* @param int $id
* @param Request $request
* @param Response $response
* @param Slug $slug
*
* @return Response
*/
public function update(
int $id,
Request $request,
Response $response,
Slug $slug,
): Response
{
$user = getUser();
$story = $this->storyRepository->getById($id);
if (! $story) {
abort(404, 'Статья не найдена!');
}
if ($story->user_id !== $user->id && ! isAdmin()) {
abort(403, 'Вы не являетесь автором данной записи!');
}
$input = (array) $request->getParsedBody();
$tags = array_map('sanitize', $input['tags'] ?? []);
$this->validator
->required(['csrf', 'title', 'text', 'tags'])
->same('csrf', $this->session->get('csrf'), 'Неверный идентификатор сессии, повторите действие!')
->length('title', setting('story.title_min_length'), setting('story.title_max_length'))
->length('text', setting('story.text_min_length'), setting('story.text_max_length'))
->custom(count($tags) <= setting('story.tags_max'), ['tags' => 'Превышено максимальное количество тегов'])
->boolean('locked');
foreach ($tags as $tag) {
$this->validator->custom(
Str::length($tag) >= setting('story.tags_min_length') && Str::length($tag) <= setting('story.tags_max_length'),
['tags' => sprintf('Длина тегов должна быть от %d до %d символов!', setting('story.tags_min_length'), setting('story.tags_max_length'))]
);
}
Tag::query()->where('story_id', $story->id)->delete();
foreach ($tags as $value) {
Tag::query()->create([
'story_id' => $story->id,
'tag' => Str::lower($value),
]);
}
if ($this->validator->isValid($input)) {
$slugify = $slug->slugify($input['title']);
$story->update([
'title' => sanitize($input['title']),
'slug' => $slugify,
'text' => sanitize($input['text']),
'locked' => isAdmin() ? $input['locked'] ?? $story->locked : $story->locked,
]);
$this->session->set('flash', ['success' => 'Статья успешно изменена!']);
return $this->redirect($response, '/' . $slugify . '-' . $id);
}
$this->session->set('flash', ['errors' => $this->validator->getErrors(), 'old' => $input]);
return $this->redirect($response, '/' . $id . '/edit');
}
/**
* Destroy
*
* @param int $id
* @param Request $request
* @param Response $response
*
* @return Response
*/
public function destroy(int $id, Request $request, Response $response): Response
{
$user = getUser();
$story = $this->storyRepository->getById($id);
if (! $story) {
abort(404, 'Статья не найдена');
}
if ($story->user_id !== $user->id && ! isAdmin()) {
abort(403, 'Вы не являетесь автором данной записи!');
}
$input = (array) $request->getParsedBody();
$this->validator
->required('csrf')
->same('csrf', $this->session->get('csrf'), 'Неверный идентификатор сессии, повторите действие!');
if ($this->validator->isValid($input)) {
$story->delete();
$this->session->set('flash', ['success' => 'Статья успешно удалена!']);
} else {
$this->session->set('flash', ['errors' => $this->validator->getErrors()]);
}
return $this->redirect($response, '/');
}
}