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