Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
NewsService
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
8 / 8
17
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getProcessedNews
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
3
 processContent
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 buildInformationLink
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getEditLink
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 getCommentMessage
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 formatNewsDate
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getAuthorInfo
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * News Service
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 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     2026-01-02
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\News;
21
22use phpMyFAQ\Configuration;
23use phpMyFAQ\Date;
24use phpMyFAQ\Enums\PermissionType;
25use phpMyFAQ\Glossary;
26use phpMyFAQ\Helper\FaqHelper;
27use phpMyFAQ\News;
28use phpMyFAQ\Strings;
29use phpMyFAQ\Translation;
30use phpMyFAQ\User\CurrentUser;
31
32/**
33 * Service class for news-related business logic.
34 */
35final class NewsService
36{
37    private News $news;
38
39    private Glossary $glossary;
40
41    private FaqHelper $faqHelper;
42
43    public function __construct(
44        private readonly Configuration $configuration,
45        private readonly CurrentUser $currentUser,
46    ) {
47        $this->news = new News($this->configuration);
48        $this->glossary = new Glossary($this->configuration);
49        $this->faqHelper = new FaqHelper($this->configuration);
50    }
51
52    /**
53     * Gets and processes news by ID.
54     *
55     * @return array<string, mixed>
56     */
57    public function getProcessedNews(int $newsId): array
58    {
59        $news = $this->news->get($newsId);
60        if ($news === []) {
61            return [];
62        }
63
64        // Process content with glossary
65        $news['processedContent'] = $this->processContent((string) ($news['content'] ?? ''));
66        $news['processedHeader'] = $this->processContent((string) ($news['header'] ?? ''));
67
68        // Add an information link if available
69        if ((string) ($news['link'] ?? '') !== '') {
70            $news['processedContent'] .= $this->buildInformationLink(
71                (string) ($news['link'] ?? ''),
72                (string) ($news['target'] ?? ''),
73                (string) ($news['linkTitle'] ?? ''),
74            );
75        }
76
77        return $news;
78    }
79
80    /**
81     * Processes content with glossary and cleanup.
82     */
83    private function processContent(string $content): string
84    {
85        $content = $this->glossary->insertItemsIntoContent($content);
86        return $this->faqHelper->cleanUpContent($content);
87    }
88
89    /**
90     * Builds the information link HTML.
91     */
92    private function buildInformationLink(string $link, string $target, string $linkTitle): string
93    {
94        return sprintf(
95            '</p><p>%s<a href="%s" target="%s">%s</a>',
96            Translation::getString('msgInfo'),
97            Strings::htmlentities($link),
98            $target,
99            Strings::htmlentities($linkTitle),
100        );
101    }
102
103    /**
104     * Checks if a user can edit news and returns an edit link.
105     */
106    public function getEditLink(int $newsId): string
107    {
108        if ($this->currentUser->perm->hasPermission(
109            $this->currentUser->getUserId(),
110            PermissionType::NEWS_EDIT->value,
111        )) {
112            return sprintf(
113                '<a href="./admin/news/edit/%d">%s</a>',
114                $newsId,
115                Translation::getString('ad_menu_news_edit'),
116            );
117        }
118
119        return '';
120    }
121
122    /**
123     * Gets a comment message based on permissions and news settings.
124     */
125    public function getCommentMessage(array $news): string
126    {
127        if (
128            -1 === $this->currentUser->getUserId() && !$this->configuration->get('records.allowCommentsForGuests')
129            || !$news['active']
130            || !$news['allowComments']
131        ) {
132            return Translation::getString('msgWriteNoComment');
133        }
134
135        return sprintf(
136            '<a href="#" data-bs-toggle="modal" data-bs-target="#pmf-modal-add-comment">%s</a>',
137            Translation::getString('newsWriteComment'),
138        );
139    }
140
141    /**
142     * Formats the news date.
143     */
144    public function formatNewsDate(array $news): string
145    {
146        if (!$news['active']) {
147            return '';
148        }
149
150        $date = new Date($this->configuration);
151        return sprintf(
152            '%s<span id="newsLastUpd">%s</span>',
153            Translation::getString('msgLastUpdateArticle'),
154            $date->format((string) ($news['date'] ?? '')),
155        );
156    }
157
158    /**
159     * Gets author information.
160     */
161    public function getAuthorInfo(array $news): string
162    {
163        if (!$news['active']) {
164            return '';
165        }
166
167        return Translation::getString('msgAuthor') . ': ' . (string) ($news['authorName'] ?? '');
168    }
169}