Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
89 / 89
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
NewsController
100.00% covered (success)
100.00%
89 / 89
100.00% covered (success)
100.00%
5 / 5
6
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%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 add
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 edit
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 getBaseTemplateVars
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * The News Administration Controller
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2024-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2024-12-01
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Administration;
21
22use phpMyFAQ\Core\Exception;
23use phpMyFAQ\Entity\CommentType;
24use phpMyFAQ\Enums\PermissionType;
25use phpMyFAQ\Filter;
26use phpMyFAQ\Helper\LanguageHelper;
27use phpMyFAQ\News;
28use phpMyFAQ\Session\Token;
29use phpMyFAQ\Translation;
30use phpMyFAQ\Twig\Extensions\FormatDateTwigExtension;
31use phpMyFAQ\Twig\Extensions\IsoDateTwigExtension;
32use Symfony\Component\HttpFoundation\Request;
33use Symfony\Component\HttpFoundation\Response;
34use Symfony\Component\Routing\Attribute\Route;
35use Twig\Error\LoaderError;
36use Twig\Extension\AttributeExtension;
37
38final class NewsController extends AbstractAdministrationController
39{
40    public function __construct(
41        private readonly News $news,
42    ) {
43        parent::__construct();
44    }
45
46    /**
47     * @throws Exception
48     * @throws LoaderError
49     * @throws \Exception
50     */
51    #[Route(path: '/news', name: 'admin.news', methods: ['GET'])]
52    public function index(Request $request): Response
53    {
54        $this->userHasPermission(PermissionType::NEWS_ADD);
55        $this->userHasPermission(PermissionType::NEWS_DELETE);
56        $this->userHasPermission(PermissionType::NEWS_EDIT);
57
58        $this->addExtension(new AttributeExtension(IsoDateTwigExtension::class));
59        $this->addExtension(new AttributeExtension(FormatDateTwigExtension::class));
60        return $this->render('@admin/content/news.twig', [
61            ...$this->getHeader($request),
62            ...$this->getFooter(),
63            ...$this->getBaseTemplateVars(),
64            'news' => $this->news->getHeader(),
65        ]);
66    }
67
68    /**
69     * @throws LoaderError
70     * @throws Exception
71     * @throws \Exception
72     */
73    #[Route(path: '/news/add', name: 'admin.news.add', methods: ['GET'])]
74    public function add(Request $request): Response
75    {
76        $this->userHasPermission(PermissionType::NEWS_ADD);
77
78        $this->addExtension(new AttributeExtension(IsoDateTwigExtension::class));
79        $this->addExtension(new AttributeExtension(FormatDateTwigExtension::class));
80        return $this->render('@admin/content/news.add.twig', [
81            ...$this->getHeader($request),
82            ...$this->getFooter(),
83            ...$this->getBaseTemplateVars(),
84            'userEmail' => $this->currentUser->getUserData('email'),
85            'userName' => $this->currentUser->getUserData('display_name'),
86        ]);
87    }
88
89    /**
90     * @throws LoaderError
91     * @throws Exception
92     * @throws \Exception
93     */
94    #[Route(path: '/news/edit/{newsId}', name: 'admin.news.edit', methods: ['GET'])]
95    public function edit(Request $request): Response
96    {
97        $this->userHasPermission(PermissionType::NEWS_ADD);
98
99        $newsId = (int) Filter::filterVar($request->attributes->get('newsId'), FILTER_VALIDATE_INT);
100
101        $newsData = $this->news->get($newsId, true);
102
103        $this->addExtension(new AttributeExtension(IsoDateTwigExtension::class));
104        $this->addExtension(new AttributeExtension(FormatDateTwigExtension::class));
105        return $this->render('@admin/content/news.edit.twig', [
106            ...$this->getHeader($request),
107            ...$this->getFooter(),
108            ...$this->getBaseTemplateVars(),
109            'newsData' => $newsData,
110            'newsDataContent' => array_key_exists('content', $newsData)
111                ? htmlspecialchars((string) $newsData['content'], ENT_QUOTES)
112                : '',
113            'newsId' => $newsId,
114            'commentTypeNews' => CommentType::NEWS,
115        ]);
116    }
117
118    /**
119     * @return array<string, mixed>
120     * @throws \Exception
121     * @todo move to Twig translation filter
122     */
123    private function getBaseTemplateVars(): array
124    {
125        $user = $this->currentUser;
126        $language = $this->configuration->getLanguage()->getLanguage();
127
128        return [
129            'permissionAddNews' => $user?->perm->hasPermission($user->getUserId(), PermissionType::NEWS_ADD),
130            'permissionEditNews' => $user->perm->hasPermission($user->getUserId(), PermissionType::NEWS_EDIT),
131            'permissionDeleteNews' => $user->perm->hasPermission($user->getUserId(), PermissionType::NEWS_DELETE),
132            'defaultUrl' => $this->configuration->getDefaultUrl(),
133            'enableWysiwyg' => $this->configuration->get(item: 'main.enableWysiwygEditor'),
134            'ad_news_add' => Translation::get(key: 'ad_news_add'),
135            'csrfToken_saveNews' => Token::getInstance($this->session)->getTokenString('save-news'),
136            'ad_news_author_name' => Translation::get(key: 'ad_news_author_name'),
137            'ad_news_set_active' => Translation::get(key: 'ad_news_set_active'),
138            'ad_news_link_url' => Translation::get(key: 'ad_news_link_url'),
139            'ad_news_link_title' => Translation::get(key: 'ad_news_link_title'),
140            'ad_news_link_target' => Translation::get(key: 'ad_news_link_target'),
141            'ad_news_link_window' => Translation::get(key: 'ad_news_link_window'),
142            'ad_news_link_faq' => Translation::get(key: 'ad_news_link_faq'),
143            'ad_news_link_parent' => Translation::get(key: 'ad_news_link_parent'),
144            'selectLanguage' => LanguageHelper::renderSelectLanguage($language, false, [], 'langTo'),
145            'ad_news_expiration_window' => Translation::get(key: 'ad_news_expiration_window'),
146            'ad_news_from' => Translation::get(key: 'ad_news_from'),
147            'ad_news_to' => Translation::get(key: 'ad_news_to'),
148            'ad_entry_back' => Translation::get(key: 'ad_entry_back'),
149            'ad_menu_news_add' => Translation::get(key: 'ad_menu_news_add'),
150            'ad_news_headline' => Translation::get(key: 'ad_news_headline'),
151            'ad_news_date' => Translation::get(key: 'ad_news_date'),
152            'ad_news_update' => Translation::get(key: 'ad_news_update'),
153            'ad_news_delete' => Translation::get(key: 'ad_news_delete'),
154            'ad_news_nodata' => Translation::get(key: 'ad_news_nodata'),
155            'ad_news_edit' => Translation::get(key: 'ad_news_edit'),
156            'ad_news_header' => Translation::get(key: 'ad_news_header'),
157            'ad_news_text' => Translation::get(key: 'ad_news_text'),
158            'ad_news_allowComments' => Translation::get(key: 'ad_news_allowComments'),
159            'msgLanguage' => Translation::get(key: 'msgLanguage'),
160            'ad_entry_comment' => Translation::get(key: 'ad_entry_comment'),
161            'ad_entry_commentby' => Translation::get(key: 'ad_entry_commentby'),
162            'newsCommentDate' => Translation::get(key: 'newsCommentDate'),
163            'ad_news_insertfail' => Translation::get(key: 'ad_news_insertfail'),
164            'msgNews' => Translation::get(key: 'msgNews'),
165            'ad_news_data' => Translation::get(key: 'ad_news_data'),
166            'ad_news_del' => Translation::get(key: 'ad_news_del'),
167            'ad_news_nodelete' => Translation::get(key: 'ad_news_nodelete'),
168            'ad_news_yesdelete' => Translation::get(key: 'ad_news_yesdelete'),
169            'ad_news_delsuc' => Translation::get(key: 'ad_news_delsuc'),
170            'ad_news_updatesuc' => Translation::get(key: 'ad_news_updatesuc'),
171            'msgDeleteNews' => Translation::get(key: 'msgDeleteNews'),
172            'csrfToken_deleteNews' => Token::getInstance($this->session)->getTokenString('delete-news'),
173            'csrfToken_updateNews' => Token::getInstance($this->session)->getTokenString('update-news'),
174            'ad_entry_active' => Translation::get(key: 'ad_entry_active'),
175            'csrfToken_activateNews' => Token::getInstance($this->session)->getTokenString('activate-news'),
176        ];
177    }
178}