Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 182
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
OlzNewsDetailParams
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
OlzNewsDetail
0.00% covered (danger)
0.00%
0 / 182
0.00% covered (danger)
0.00%
0 / 5
870
0.00% covered (danger)
0.00%
0 / 1
 hasAccess
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSearchTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 searchSqlWhenHasAccess
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
2
 getHtmlWhenHasAccess
0.00% covered (danger)
0.00%
0 / 155
0.00% covered (danger)
0.00%
0 / 1
650
 getNewsEntryById
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3// =============================================================================
4// Alle Neuigkeiten rund um die OL Zimmerberg
5// =============================================================================
6
7namespace Olz\News\Components\OlzNewsDetail;
8
9use Doctrine\Common\Collections\Criteria;
10use Olz\Components\Common\OlzRootComponent;
11use Olz\Components\Page\OlzFooter\OlzFooter;
12use Olz\Components\Page\OlzHeader\OlzHeader;
13use Olz\Entity\News\NewsEntry;
14use Olz\News\Components\OlzArticleMetadata\OlzArticleMetadata;
15use Olz\News\Components\OlzAuthorBadge\OlzAuthorBadge;
16use Olz\News\Utils\NewsUtils;
17use Olz\Utils\HttpParams;
18
19/** @extends HttpParams<array{von?: ?string}> */
20class OlzNewsDetailParams extends HttpParams {
21}
22
23/** @extends OlzRootComponent<array<string, mixed>> */
24class OlzNewsDetail extends OlzRootComponent {
25    public function hasAccess(): bool {
26        return true;
27    }
28
29    public function getSearchTitle(): string {
30        return 'News';
31    }
32
33    public function searchSqlWhenHasAccess(array $terms): ?string {
34        $code_href = $this->envUtils()->getCodeHref();
35        $today_iso = $this->dateUtils()->getIsoToday();
36        $where = implode(' AND ', array_map(function ($term) {
37            $date_sql = $this->searchUtils()->getDateSql('published_date', $term) ?? '0';
38            return <<<ZZZZZZZZZZ
39                (
40                    title LIKE '%{$term}%'
41                    OR teaser LIKE '%{$term}%'
42                    OR content LIKE '%{$term}%'
43                    OR {$date_sql}
44                )
45                ZZZZZZZZZZ;
46        }, $terms));
47        return <<<ZZZZZZZZZZ
48            WITH
49                base AS (
50                    SELECT
51                        CONCAT('{$code_href}news/', id) AS link,
52                        CONCAT('{$code_href}assets/icns/entry_type_', format, '_20.svg') AS icon,
53                        published_date AS date,
54                        title AS title,
55                        CONCAT(IFNULL(teaser, ''), ' ', IFNULL(content, '')) AS text,
56                        DATEDIFF(published_date, '{$today_iso}') AS diffdays
57                    FROM news
58                    WHERE
59                        on_off = '1'
60                        AND {$this->newsUtils()->getIsNotArchivedSql()}
61                        AND {$where}
62                )
63            SELECT
64                *,
65                CASE
66                    WHEN diffdays < -400 THEN 0.7
67                    WHEN diffdays < -100 THEN 1.0 + (diffdays + 100) * 0.3 / 300.0
68                    WHEN diffdays < 100 THEN 1.0
69                    ELSE 0.1
70                END AS time_relevance
71            FROM base
72            ZZZZZZZZZZ;
73    }
74
75    public function getHtmlWhenHasAccess(mixed $args): string {
76        $this->httpUtils()->validateGetParams(OlzNewsDetailParams::class);
77        $code_href = $this->envUtils()->getCodeHref();
78        $db = $this->dbUtils()->getDb();
79        $entityManager = $this->dbUtils()->getEntityManager();
80        $user = $this->authUtils()->getCurrentUser();
81        $id = $args['id'] ?? null;
82
83        $news_repo = $entityManager->getRepository(NewsEntry::class);
84        $is_not_archived = $this->newsUtils()->getIsNotArchivedCriteria();
85        $criteria = Criteria::create()
86            ->where(Criteria::expr()->andX(
87                $is_not_archived,
88                Criteria::expr()->eq('id', $id),
89                Criteria::expr()->eq('on_off', 1),
90            ))
91            ->setFirstResult(0)
92            ->setMaxResults(1)
93        ;
94        $news_entries = $news_repo->matching($criteria);
95        $num_news_entries = $news_entries->count();
96        $is_archived = $num_news_entries !== 1;
97
98        if ($is_archived && !$this->authUtils()->hasPermission('any')) {
99            $this->httpUtils()->dieWithHttpError(404);
100            throw new \Exception('should already have failed');
101        }
102
103        $article_metadata = "";
104        try {
105            $article_metadata = OlzArticleMetadata::render(['id' => $id]);
106        } catch (\Exception $exc) {
107            $this->httpUtils()->dieWithHttpError(404);
108            throw new \Exception('should already have failed');
109        }
110
111        $news_entry = $this->getNewsEntryById($id);
112
113        if (!$news_entry) {
114            $this->httpUtils()->dieWithHttpError(404);
115            throw new \Exception('should already have failed');
116        }
117
118        $title = $news_entry->getTitle();
119        $out = OlzHeader::render([
120            'back_link' => "{$code_href}news",
121            'title' => "{$title} - News",
122            'description' => "Aktuelle Beiträge, Berichte von Anlässen und weitere Neuigkeiten von der OL Zimmerberg.",
123            'norobots' => $is_archived,
124            'canonical_url' => "{$code_href}news/{$id}",
125            'additional_headers' => [
126                $article_metadata,
127            ],
128        ]);
129
130        $format = $news_entry->getFormat();
131        // TODO: Use array_find with PHP 8.4
132        $filtered = array_filter(
133            NewsUtils::ALL_FORMAT_OPTIONS,
134            fn ($entry) => $entry['ident'] === $format
135        );
136        // @phpstan-ignore-next-line
137        $found_entry = $filtered[array_keys($filtered)[0]];
138        $name = $found_entry['name'];
139        $icon = $found_entry['icon'] ?? null;
140        $icon_html = "<img src='{$code_href}assets/icns/{$icon}' alt='' class='format-icon'>";
141        $pretty_format = "{$icon_html}{$name}";
142
143        $pretty_date = $this->dateUtils()->olzDate("tt.mm.jjjj", $news_entry->getPublishedDate());
144        $author_user = $news_entry->getAuthorUser();
145        $author_role = $news_entry->getAuthorRole();
146        $author_name = $news_entry->getAuthorName();
147        $author_email = $news_entry->getAuthorEmail();
148        $pretty_author = OlzAuthorBadge::render([
149            'news_id' => $news_entry->getId() ?: 0,
150            'user' => $author_user,
151            'role' => $author_role,
152            'name' => $author_name,
153            'email' => $author_email,
154        ]);
155        $image_ids = $news_entry->getImageIds();
156        $num_images = count($image_ids);
157        $download_all_link = $this->authUtils()->hasPermission('any')
158            ? "<a href='{$code_href}news/{$id}/all.zip'>Alle herunterladen</a>" : '';
159
160        $out .= <<<ZZZZZZZZZZ
161            <div class='content-right'>
162                <div style='padding:4px 3px 10px 3px;'>
163                    <div id='format-info'><b>Format: </b>{$pretty_format}</div>
164                    <div><b>Datum: </b>{$pretty_date}</div>
165                    <div><b>Autor: </b>{$pretty_author}</div>
166                    <div><b>Anzahl Bilder: </b>{$num_images}</div>
167                    <div class='pretty'>{$download_all_link}</div>
168                </div>
169            </div>
170            <div class='content-middle'>
171            ZZZZZZZZZZ;
172
173        $db->query("UPDATE news SET `counter`=`counter` + 1 WHERE `id`='{$id}'");
174
175        $title = $news_entry->getTitle();
176        $teaser = $news_entry->getTeaser() ?? '';
177        $content = $news_entry->getContent() ?? '';
178        $published_date = $news_entry->getPublishedDate();
179
180        $published_date = $this->dateUtils()->olzDate("tt.mm.jj", $published_date);
181
182        $is_owner = $user && intval($news_entry->getOwnerUser()?->getId() ?? 0) === intval($user->getId());
183        $has_all_permissions = $this->authUtils()->hasPermission('all');
184        $can_edit = $is_owner || $has_all_permissions;
185        $edit_admin = '';
186        if ($can_edit) {
187            $json_id = json_encode($id);
188            $has_blog = $this->authUtils()->hasPermission('kaderblog', $user);
189            $has_roles = !empty($this->authUtils()->getAuthenticatedRoles());
190            $json_mode = htmlentities(json_encode($has_roles ? ($has_blog ? 'account_with_all' : 'account_with_aktuell') : ($has_blog ? 'account_with_blog' : 'account')) ?: '');
191            $edit_admin = <<<ZZZZZZZZZZ
192                <div>
193                    <button
194                        id='edit-news-button'
195                        class='btn btn-primary'
196                        onclick='return olz.editNews({$json_id}{$json_mode})'
197                    >
198                        <img src='{$code_href}assets/icns/edit_white_16.svg' class='noborder' />
199                        Bearbeiten
200                    </button>
201                </div>
202                ZZZZZZZZZZ;
203        }
204
205        // TODO: Temporary fix for broken Markdown
206        $content = str_replace("\n", "\n\n", $content);
207        $content = str_replace("\n\n\n\n", "\n\n", $content);
208
209        // Markdown
210        $html_input = $format === 'forum' ? 'escape' : 'allow'; // TODO: Do NOT allow!
211        $teaser = $this->htmlUtils()->renderMarkdown($teaser, [
212            'html_input' => $html_input,
213        ]);
214        $content = $this->htmlUtils()->renderMarkdown($content, [
215            'html_input' => $html_input,
216        ]);
217
218        // Datei- & Bildpfade
219        $teaser = $news_entry->replaceImagePaths($teaser);
220        $teaser = $news_entry->replaceFilePaths($teaser);
221        $content = $news_entry->replaceImagePaths($content);
222        $content = $news_entry->replaceFilePaths($content);
223
224        $out .= "<h1>{$edit_admin}{$title}</h1>";
225
226        $gallery = '';
227        $num_images = count($image_ids);
228        if ($num_images > 0) {
229            $gallery .= "<div class='lightgallery gallery-container'>";
230            foreach ($image_ids as $image_id) {
231                $gallery .= "<div class='gallery-image'>";
232                $gallery .= $this->imageUtils()->olzImage(
233                    'news',
234                    $id,
235                    $image_id,
236                    110,
237                    'gallery[myset]'
238                );
239                $gallery .= "</div>";
240            }
241            $gallery .= "</div>";
242        }
243
244        if ($format === 'aktuell') {
245            $out .= "<p><b>{$teaser}</b><p>{$content}</p><br/><br/>{$gallery}\n";
246        } elseif ($format === 'kaderblog') {
247            $out .= "<p>{$content}</p><br/><br/>{$gallery}\n";
248        } elseif ($format === 'forum') {
249            $out .= "<p><b>{$teaser}</b><p>{$content}</p><br/><br/>{$gallery}\n";
250        } elseif ($format === 'galerie') {
251            $out .= "<p>{$content}</p>{$gallery}\n";
252        } elseif ($format === 'video') {
253            $youtube_url = $news_entry->getExternalUrl() ?? '';
254            $res0 = preg_match("/^https\\:\\/\\/(www\\.)?youtu\\.be\\/([a-zA-Z0-9\\-\\_]{6,})/", $youtube_url, $matches0);
255            $res1 = preg_match("/^https\\:\\/\\/(www\\.)?youtube\\.com\\/watch\\?v\\=([a-zA-Z0-9\\-\\_]{6,})/", $youtube_url, $matches1);
256            $youtube_match = null;
257            if ($res0) {
258                $youtube_match = $matches0[2];
259            }
260            if ($res1) {
261                $youtube_match = $matches1[2];
262            }
263
264            $out .= "<div class='video-container'>";
265            $out .= "<div style='background-image:url({$code_href}assets/icns/movie_dot.svg);background-repeat:repeat-x;margin:0px;padding:0px;height:24px;'></div>\n";
266            if ($youtube_match != null) {
267                $out .= "<iframe width='560' height='315' src='https://www.youtube.com/embed/{$youtube_match}' frameborder='0' allow='accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture' allowfullscreen></iframe>";
268            } else {
269                $this->log()->error("Invalid YouTube link (ID:{$id}): {$youtube_url}");
270                $out .= "Fehlerhafter YouTube-Link!";
271            }
272            $out .= "<div style='background-image:url({$code_href}assets/icns/movie_dot.svg);background-repeat:repeat-x;margin:0px;padding:0px;height:24px;'></div>";
273            $out .= "</div>";
274        } else {
275            $out .= "<div class='lightgallery'><p><b>{$teaser}</b><p>{$content}</p></div>\n";
276        }
277        $out .= "</div>";
278
279        $out .= OlzFooter::render();
280
281        return $out;
282    }
283
284    protected function getNewsEntryById(int $id): ?NewsEntry {
285        $news_repo = $this->entityManager()->getRepository(NewsEntry::class);
286        return $news_repo->findOneBy([
287            'id' => $id,
288            'on_off' => 1,
289        ]);
290    }
291}