Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
NewsController
100.00% covered (success)
100.00%
74 / 74
100.00% covered (success)
100.00%
8 / 8
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 index
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 meetings
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 show
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 comment
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
1
 deleteComment
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
3
 showOverview
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
2
 renderView
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Controllers;
6
7use Engelsystem\Config\Config;
8use Engelsystem\Helpers\Authenticator;
9use Engelsystem\Http\Exceptions\HttpForbidden;
10use Engelsystem\Http\Redirector;
11use Engelsystem\Http\Request;
12use Engelsystem\Http\Response;
13use Engelsystem\Models\News;
14use Engelsystem\Models\NewsComment;
15use Psr\Log\LoggerInterface;
16
17class NewsController extends BaseController
18{
19    use HasUserNotifications;
20
21    /** @var array<string, string> */
22    protected array $permissions = [
23        'news',
24        'meetings'      => 'user_meetings',
25        'comment'       => 'news_comments',
26        'deleteComment' => 'news_comments',
27    ];
28
29    public function __construct(
30        protected Authenticator $auth,
31        protected NewsComment $comment,
32        protected Config $config,
33        protected LoggerInterface $log,
34        protected News $news,
35        protected Redirector $redirect,
36        protected Response $response,
37        protected Request $request
38    ) {
39    }
40
41    public function index(): Response
42    {
43        return $this->showOverview();
44    }
45
46    public function meetings(): Response
47    {
48        return $this->showOverview(true);
49    }
50
51    public function show(Request $request): Response
52    {
53        $newsId = (int) $request->getAttribute('news_id');
54
55        $news = $this->news
56            ->with(['user', 'comments.user.state', 'comments.user.personalData'])
57            ->findOrFail($newsId);
58
59        return $this->renderView('pages/news/news.twig', ['news' => $news]);
60    }
61
62    public function comment(Request $request): Response
63    {
64        $newsId = (int) $request->getAttribute('news_id');
65
66        $data = $this->validate($request, [
67            'comment' => 'required',
68        ]);
69        $user = $this->auth->user();
70        $news = $this->news->findOrFail($newsId);
71
72        /** @var NewsComment $comment */
73        $comment = $news->comments()->create([
74            'text'    => $data['comment'],
75            'user_id' => $user->id,
76        ]);
77
78        $this->log->info(
79            'Created news comment for "{news}": {comment}',
80            [
81                'news'    => $news->title,
82                'comment' => $comment->text,
83            ]
84        );
85
86        $this->addNotification('news.comment.success');
87
88        return $this->redirect->back();
89    }
90
91    public function deleteComment(Request $request): Response
92    {
93        $commentId = (int) $request->getAttribute('comment_id');
94
95        $this->validate(
96            $request,
97            [
98                'delete' => 'checked',
99            ]
100        );
101
102        $comment = $this->comment->findOrFail($commentId);
103        if (
104            $comment->user->id != $this->auth->user()->id
105            && !$this->auth->canAny(['admin_news', 'comment.delete'])
106        ) {
107            throw new HttpForbidden();
108        }
109
110        $comment->delete();
111
112        $this->log->info(
113            'Deleted comment "{comment}" of news "{news}"',
114            ['comment' => $comment->text, 'news' => $comment->news->title]
115        );
116        $this->addNotification('news.comment-delete.success');
117
118        return $this->redirect->to('/news/' . $comment->news->id);
119    }
120
121    protected function showOverview(bool $onlyMeetings = false): Response
122    {
123        $query = $this->news;
124        $page = $this->request->get('page', 1);
125        $perPage = $this->config->get('display_news');
126
127        if ($onlyMeetings) {
128            $query = $query->where('is_meeting', true);
129        }
130
131        $count = $query->count();
132        $pagesCount = max(1, ceil($count / $perPage));
133        $page = max(1, min($page, $pagesCount));
134
135        $news = $query
136            ->with('user')
137            ->withCount('comments')
138            ->orderByDesc('is_pinned')
139            ->orderByDesc('is_highlighted')
140            ->orderByDesc('updated_at')
141            ->orderByDesc('id')
142            ->limit($perPage)
143            ->offset(($page - 1) * $perPage)
144            ->get();
145
146        return $this->renderView(
147            'pages/news/index.twig',
148            [
149                'news'          => $news,
150                'pages'         => $pagesCount,
151                'page'          => $page,
152                'only_meetings' => $onlyMeetings,
153                'is_overview'   => true,
154            ]
155        );
156    }
157
158    protected function renderView(string $page, array $data): Response
159    {
160        return $this->response->withView($page, $data);
161    }
162}