Файл: app/Traits/HandlesComments.php
Строк: 270
<?php
declare(strict_types=1);
namespace AppTraits;
use AppClassesValidator;
use AppModelsComment;
use AppModelsFile;
use AppModelsFlood;
use AppModelsUser;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsMorphMany;
use IlluminateDatabaseQueryJoinClause;
use IlluminateHttpJsonResponse;
use IlluminateHttpRedirectResponse;
use IlluminateHttpRequest;
use IlluminateSupportCollection;
use IlluminateSupportStr;
trait HandlesComments
{
/**
* Связь комментариев модели (morph-имя relate единое по движку)
*
* @return MorphMany<Comment, Model>
*/
private function commentsRelation(Model $model): MorphMany
{
return $model->morphMany(Comment::class, 'relate')->with('relate');
}
/**
* Возвращает [routeName, params] для редиректа на страницу модели
*/
protected function commentableViewRoute(Model $model): array
{
$plural = Str::plural(Str::snake(class_basename($this->commentableModelClass)));
return [$plural . '.view', ['id' => $model->getKey()]];
}
/**
* Находит родительскую модель по id или прерывает с 404
*/
protected function findCommentParent(int $id): Model
{
$model = ($this->commentableModelClass)::query()->find($id);
if (! $model) {
abort(404, __('main.record_not_found'));
}
return $model;
}
/**
* Проверяет доступность модели (прерывает если active = false)
*/
protected function checkCommentableModel(Model $model): void
{
if (array_key_exists('active', $model->getAttributes()) && ! $model->getAttribute('active')) {
abort(200, __('main.record_not_active'));
}
}
/**
* Возвращает комментарии в виде дерева и файлы для передачи во вью
*/
protected function getCommentsData(Model $model): array
{
$user = getUser();
$files = $user
? File::query()
->where('relate_type', Comment::$morphName)
->where('relate_id', 0)
->where('user_id', $user->id)
->orderBy('created_at')
->get()
: collect();
$allComments = $this->commentsRelation($model)
->withoutGlobalScope('active')
->select('comments.*', 'polls.vote')
->leftJoin('polls', static function (JoinClause $join) {
$join->on('comments.id', 'polls.relate_id')
->where('polls.relate_type', Comment::$morphName)
->where('polls.user_id', getUser('id'));
})
->orderBy('created_at')
->with(['user', 'files'])
->get();
$comments = $this->buildCommentTree($allComments);
return compact('comments', 'files');
}
/**
* Строит дерево комментариев из плоской коллекции
*/
protected function buildCommentTree(Collection $all, ?int $parentId = null): Collection
{
return $all
->where('parent_id', $parentId)
->map(function (Comment $comment) use ($all) {
$comment->setRelation('children', $this->buildCommentTree($all, $comment->id));
return $comment;
})
->values();
}
/**
* Редирект на страницу с якорем на добавленный комментарий
*/
protected function redirectAfterCommentAdded(Model $model, int $commentId): RedirectResponse
{
[$route, $params] = $this->commentableViewRoute($model);
return redirect()
->route($route, $params)
->withFragment('comment_' . $commentId);
}
/**
* Добавление комментария (POST-обработчик)
*/
public function storeComment(int $id, Request $request, Validator $validator, Flood $flood): RedirectResponse|JsonResponse
{
$model = $this->findCommentParent($id);
[$viewRoute, $viewParams] = $this->commentableViewRoute($model);
$user = getUser();
$msg = $request->input('msg');
$validator
->true($user, __('main.not_authorized'))
->false($flood->isFlood(), ['msg' => __('validator.flood', ['sec' => $flood->getPeriod()])])
->length($msg, setting('comment_text_min'), setting('comment_text_max'), ['msg' => __('validator.text')]);
$validator->empty($model->getAttribute('closed'), ['msg' => __('main.closed_comments')]);
if ($validator->isValid()) {
$msg = antimat($msg);
$parentId = null;
$depth = 0;
$parentComment = $request->input('parent_id')
? Comment::query()->find((int) $request->input('parent_id'))
: null;
if ($parentComment && $parentComment->relate_id === $model->getKey()) {
if ($parentComment->depth >= setting('comment_depth')) {
$parentId = $parentComment->parent_id;
$depth = $parentComment->depth;
} else {
$parentId = $parentComment->id;
$depth = $parentComment->depth + 1;
}
}
$comment = $this->commentsRelation($model)->create([
'text' => $msg,
'user_id' => $user->id,
'parent_id' => $parentId,
'depth' => $depth,
'ip' => getIp(),
'brow' => getBrowser(),
]);
File::query()
->where('relate_type', Comment::$morphName)
->where('relate_id', 0)
->where('user_id', $user->id)
->update(['relate_id' => $comment->id]);
$user->increment('point', setting('comment_point'));
$user->increment('money', setting('comment_money'));
$model->increment('count_comments');
$flood->saveState();
$this->notifyAfterComment($model, $comment, $msg, $viewRoute, $viewParams, $parentComment);
if ($request->wantsJson()) {
return response()->json(['redirect' => route($viewRoute, $viewParams) . '#comment_' . $comment->id]);
}
setFlash('success', __('main.comment_added_success'));
return $this->redirectAfterCommentAdded($model, $comment->id);
}
if ($request->wantsJson()) {
return response()->json(['errors' => $validator->getErrors()], 422);
}
setInput($request->all());
setFlash('danger', $validator->getErrors());
return redirect()->route($viewRoute, $viewParams);
}
/**
* Уведомление о добавлении комментария
*/
private function notifyAfterComment(Model $model, Comment $comment, string $msg, string $viewRoute, array $viewParams, ?Comment $parentComment): void
{
$url = route($viewRoute, $viewParams, false) . '#comment_' . $comment->id;
$title = (string) $model->getAttribute('title');
$skip = [];
$owner = $model->getRelationValue('user');
$owner = $owner instanceof User && $owner->exists ? $owner : null;
if ($owner && $owner->notify_comment && $owner->id !== getUser('id') && $parentComment === null) {
$login = getUser('login');
$owner->sendMessage(null, textNotice('comment_added', compact('login', 'url', 'title') + ['text' => $msg]));
$skip[] = $owner->login;
}
$replyUser = $parentComment?->user?->exists ? $parentComment->user : null;
if ($replyUser && ! in_array($replyUser->login, $skip, true) && $replyUser->notify_reply && $replyUser->id !== getUser('id')) {
$login = getUser('login');
$replyUser->sendMessage(null, textNotice('comment_reply', compact('login', 'url', 'title') + ['text' => $msg]));
$skip[] = $replyUser->login;
}
sendNotify($msg, $url, $title, $skip);
}
}