Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 261
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
OlzTerminDetailParams
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
OlzTerminDetail
0.00% covered (danger)
0.00%
0 / 261
0.00% covered (danger)
0.00%
0 / 7
3540
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
 searchSqlWhenHasAccess
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
6
 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 / 215
0.00% covered (danger)
0.00%
0 / 1
2550
 getTerminById
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getTimeText
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace Olz\Termine\Components\OlzTerminDetail;
4
5use Doctrine\Common\Collections\Criteria;
6use Olz\Components\Common\OlzLocationMap\OlzLocationMap;
7use Olz\Components\Common\OlzRootComponent;
8use Olz\Components\Page\OlzFooter\OlzFooter;
9use Olz\Components\Page\OlzHeader\OlzHeader;
10use Olz\Components\Schema\OlzEventData\OlzEventData;
11use Olz\Entity\Termine\Termin;
12use Olz\Entity\Termine\TerminLabel;
13use Olz\Termine\Components\OlzDateCalendar\OlzDateCalendar;
14use Olz\Users\Components\OlzUserInfoModal\OlzUserInfoModal;
15use Olz\Utils\HttpParams;
16
17/** @extends HttpParams<array{von?: ?string}> */
18class OlzTerminDetailParams extends HttpParams {
19}
20
21/** @extends OlzRootComponent<array<string, mixed>> */
22class OlzTerminDetail extends OlzRootComponent {
23    public function hasAccess(): bool {
24        return true;
25    }
26
27    public function searchSqlWhenHasAccess(array $terms): string|array|null {
28        $code_href = $this->envUtils()->getCodeHref();
29        $today_iso = $this->dateUtils()->getIsoToday();
30        $where = implode(' AND ', array_map(function ($term) {
31            $date_sql = '';
32            $term_date_range = $this->dateUtils()->parseDateTimeRange($term);
33            if ($term_date_range !== null) {
34                $query_start = $term_date_range['start']->format('Y-m-d');
35                $query_end = $term_date_range['end']->format('Y-m-d');
36                $date_sql = <<<ZZZZZZZZZZ
37                    OR (
38                        t.start_date < '{$query_end}'
39                        AND IFNULL(t.end_date, t.start_date) >= '{$query_start}'
40                    )
41                    ZZZZZZZZZZ;
42            }
43            return <<<ZZZZZZZZZZ
44                (
45                    t.title LIKE '%{$term}%'
46                    OR t.text LIKE '%{$term}%'
47                    OR tl.name LIKE '%{$term}%'
48                    {$date_sql}
49                )
50                ZZZZZZZZZZ;
51        }, $terms));
52        return [
53            'with' => [
54                <<<ZZZZZZZZZZ
55                    base_termine AS (
56                        SELECT
57                            CONCAT('{$code_href}termine/', t.id) AS link,
58                            '{$code_href}assets/icns/termine_type_all_20.svg' AS icon,
59                            t.start_date AS date,
60                            CONCAT('Termin: ', t.title) AS title,
61                            CONCAT(IFNULL(tl.name, ''), ' ', IFNULL(t.text, ''), ' Typ: ', (
62                                SELECT GROUP_CONCAT(l.name ORDER BY l.position ASC SEPARATOR ', ')
63                                FROM
64                                    termin_label_map tlm
65                                    JOIN termin_labels l ON (l.id = tlm.label_id)
66                                WHERE tlm.termin_id = t.id
67                                GROUP BY t.id
68                            )) AS text,
69                            DATEDIFF(t.start_date, '{$today_iso}') AS diffdays
70                        FROM termine t LEFT JOIN termin_locations tl ON (t.location_id = tl.id)
71                        WHERE
72                            t.on_off = '1'
73                            AND {$this->termineUtils()->getIsNotArchivedSql('t')}
74                            AND {$where}
75                    )
76                    ZZZZZZZZZZ,
77            ],
78            'query' => <<<'ZZZZZZZZZZ'
79                SELECT
80                    link, icon, date, title, text,
81                    CASE
82                        WHEN diffdays < -30 THEN 0.7
83                        WHEN diffdays <= 0 THEN 1.0 + diffdays * 0.3 / 30.0
84                        WHEN diffdays < 100 THEN 1.0
85                        WHEN diffdays < 400 THEN 1.0 - (diffdays - 100) * 0.3 / 300.0
86                        ELSE 0.7
87                    END AS time_relevance
88                FROM base_termine
89                ZZZZZZZZZZ,
90        ];
91    }
92
93    public function getPageTitle(): string {
94        return "";
95    }
96
97    public function getPageDescription(): string {
98        return "";
99    }
100
101    public function getHtmlWhenHasAccess(mixed $args): string {
102        $this->httpUtils()->validateGetParams(OlzTerminDetailParams::class);
103
104        $code_href = $this->envUtils()->getCodeHref();
105        $code_path = $this->envUtils()->getCodePath();
106        $data_path = $this->envUtils()->getDataPath();
107        $date_utils = $this->dateUtils();
108        $today = $date_utils->getIsoToday();
109        $entityManager = $this->dbUtils()->getEntityManager();
110        $user = $this->authUtils()->getCurrentUser();
111        $id = $args['id'] ?? null;
112
113        $termin_repo = $entityManager->getRepository(Termin::class);
114        $is_not_archived = $this->termineUtils()->getIsNotArchivedCriteria();
115        $criteria = Criteria::create()
116            ->where(Criteria::expr()->andX(
117                $is_not_archived,
118                Criteria::expr()->eq('id', $id),
119                Criteria::expr()->eq('on_off', 1),
120            ))
121            ->setFirstResult(0)
122            ->setMaxResults(1)
123        ;
124        $termine = $termin_repo->matching($criteria);
125        $num_termine = $termine->count();
126        $is_archived = $num_termine !== 1;
127
128        if ($is_archived && !$this->authUtils()->hasPermission('any')) {
129            $this->httpUtils()->dieWithHttpError(404);
130            throw new \Exception('should already have failed');
131        }
132
133        $termin = $this->getTerminById($id);
134
135        if (!$termin) {
136            $this->httpUtils()->dieWithHttpError(404);
137            throw new \Exception('should already have failed');
138        }
139
140        $title = $termin->getTitle() ?? '';
141        $termin_year = $termin->getStartDate()->format('Y');
142        $this_year = $this->dateUtils()->getCurrentDateInFormat('Y');
143        $maybe_date = ($termin_year !== $this_year) ? " {$termin_year}" : '';
144        $title = "{$title}{$maybe_date}";
145        $text = $termin->getText() ?? '';
146        $out = OlzHeader::render([
147            'back_link' => "{$code_href}termine",
148            'title' => "{$title} - Termine",
149            'description' => "{$text}",
150            'norobots' => $is_archived,
151            'canonical_url' => "{$code_href}termine/{$id}",
152        ]);
153
154        $out .= <<<'ZZZZZZZZZZ'
155            <div class='content-right optional'>
156                <div style='padding:4px 3px 10px 3px;'>
157                </div>
158            </div>
159            <div class='content-middle'>
160            ZZZZZZZZZZ;
161
162        $start_date = $termin->getStartDate();
163        $end_date = $termin->getEndDate() ?? null;
164        $start_time = $termin->getStartTime() ?? null;
165        $end_time = $termin->getEndTime() ?? null;
166        $organizer = $termin->getOrganizerUser();
167        $labels = [...$termin->getLabels()];
168        $latitude = $termin->getLatitude() ?? 0;
169        $longitude = $termin->getLongitude() ?? 0;
170        $solv_uid = $termin->getSolvId();
171        $termin_location = $termin->getLocation();
172        $has_olz_location = ($latitude > 0 && $longitude > 0);
173        $has_termin_location = (
174            $termin_location
175            && $termin_location->getLatitude() > 0
176            && $termin_location->getLongitude() > 0
177        );
178        $lat = null;
179        $lng = null;
180        $location_name = null;
181        if ($has_termin_location) {
182            $lat = $termin_location->getLatitude();
183            $lng = $termin_location->getLongitude();
184            $location_name = $termin_location->getName();
185        }
186        if ($has_olz_location) {
187            $lat = $latitude;
188            $lng = $longitude;
189            $location_name = null;
190        }
191        $has_location = $has_olz_location || $has_termin_location;
192        $image_ids = $termin->getImageIds();
193
194        $out .= OlzEventData::render([
195            'name' => $title,
196            'start_date' => $date_utils->olzDate('jjjj-mm-tt', $start_date),
197            'end_date' => $end_date ? $date_utils->olzDate('jjjj-mm-tt', $end_date) : null,
198            'location' => $has_location ? [
199                'lat' => $lat,
200                'lng' => $lng,
201                'name' => $location_name,
202            ] : null,
203        ]);
204
205        $out .= "<div class='olz-termin-detail'>";
206
207        $out .= "<div class='preview'>";
208        // Bild anzeigen
209        if (count($image_ids) > 0) {
210            $out .= $this->imageUtils()->olzImage(
211                'termine',
212                $id,
213                $image_ids[0],
214                840
215            );
216        // Karte zeigen
217        } elseif ($has_location) {
218            $out .= OlzLocationMap::render([
219                'latitude' => $lat,
220                'longitude' => $lng,
221                'zoom' => 13,
222            ]);
223        }
224        // Date Calendar Icon
225        $out .= "<div class='date-calendar-container'>";
226        $out .= "<div class='date-calendars'>";
227        $out .= "<div class='date-calendar'>";
228        $out .= OlzDateCalendar::render(['date' => $start_date]);
229        $out .= $this->getTimeText($start_time) ?? '';
230        $out .= ($end_time && (!$end_date || $end_date === $start_date))
231            ? ' &ndash; '.$this->getTimeText($end_time)
232            : '';
233        $out .= "</div>";
234        $out .= "<div class='date-calendar'>";
235        $out .= ($end_date && $end_date !== $start_date)
236            ? OlzDateCalendar::render(['date' => $end_date])
237            : '';
238        $out .= ($end_time && $end_date && $end_date !== $start_date)
239            ? $this->getTimeText($end_time)
240            : '';
241        $out .= "</div>";
242        $out .= "</div>";
243        $out .= "</div>";
244
245        $out .= "</div>";
246
247        // Editing Tools
248        $is_owner = $user && intval($termin->getOwnerUser()?->getId() ?? 0) === intval($user->getId());
249        $is_organizer = $user && intval($termin->getOrganizerUser()?->getId() ?? 0) === intval($user->getId());
250        $has_termine_permissions = $this->authUtils()->hasPermission('termine');
251        $can_edit = $is_owner || $is_organizer || $has_termine_permissions;
252        if ($can_edit) {
253            $json_id = json_encode($id);
254            $out .= <<<ZZZZZZZZZZ
255                <div>
256                    <button
257                        id='edit-termin-button'
258                        class='btn btn-primary'
259                        onclick='return olz.editTermin({$json_id})'
260                    >
261                        <img src='{$code_href}assets/icns/edit_white_16.svg' class='noborder' />
262                        Bearbeiten
263                    </button>
264                </div>
265                ZZZZZZZZZZ;
266        }
267
268        // Date & Title
269        $pretty_date = $this->dateUtils()->formatDateTimeRange(
270            $start_date->format('Y-m-d'),
271            $start_time?->format('H:i:s'),
272            $end_date?->format('Y-m-d'),
273            $end_time?->format('H:i:s'),
274            $format = 'long',
275        );
276        $maybe_solv_link = '';
277        if ($solv_uid) {
278            // SOLV-Übersicht-Link zeigen
279            $maybe_solv_link .= "<a href='https://www.o-l.ch/cgi-bin/fixtures?&mode=show&unique_id={$solv_uid}' target='_blank' class='linkol' style='margin-left: 20px; font-weight: normal;'>O-L.ch</a>\n";
280        }
281        $label_imgs = implode('', array_map(function (TerminLabel $label) use ($code_path, $code_href) {
282            $ident = $label->getIdent();
283            // TODO: Remove fallback mechanism?
284            $fallback_path = "{$code_path}assets/icns/termine_type_{$ident}_20.svg";
285            $fallback_href = is_file($fallback_path)
286                ? "{$code_href}assets/icns/termine_type_{$ident}_20.svg" : null;
287            $icon_href = $label->getIcon() ? $label->getFileHref($label->getIcon()) : $fallback_href;
288            return $icon_href ? "<img src='{$icon_href}' alt='' class='type-icon'>" : '';
289        }, $labels));
290        $out .= "<h5>{$pretty_date}{$maybe_solv_link}</h5>";
291        $out .= "<h1>{$title} {$label_imgs}</h1>";
292        if ($organizer) {
293            $pretty_organizer = OlzUserInfoModal::render(['user' => $organizer]);
294            $out .= "<div>Organisator: {$pretty_organizer}</div><br>";
295        }
296
297        // Text
298        $text_html = $this->htmlUtils()->renderMarkdown($text, [
299            // TODO: Do NOT ever allow!
300            'html_input' => $start_date->format('Y') > '2020' ? 'escape' : 'allow',
301        ]);
302        $text_html = $termin->replaceImagePaths($text_html);
303        $text_html = $termin->replaceFilePaths($text_html);
304        if ($termin->getDeadline() && $termin->getDeadline() != "0000-00-00") {
305            $text_html .= ($text_html == "" ? "" : "<br />")."Meldeschluss: ".$date_utils->olzDate("t. MM ", $termin->getDeadline());
306        }
307        $out .= "<div>".$text_html."</div>";
308
309        // Link
310        $link = '';
311        if ($solv_uid && $start_date->format('Y-m-d') <= $today && !preg_match('/(Rangliste|Resultat)/', $link)) {
312            // SOLV Ranglisten-Link zeigen
313            $link .= "<div><a href='http://www.o-l.ch/cgi-bin/results?unique_id={$solv_uid}&club=zimmerberg' target='_blank' class='linkol'>Rangliste</a></div>\n";
314        }
315        $result_filename = "{$termin_year}-termine-{$id}.xml";
316        if (is_file("{$data_path}results/{$result_filename}")) {
317            // OLZ Ranglisten-Link zeigen
318            $link .= "<div><a href='{$code_href}apps/resultate?file={$result_filename}' target='_blank' class='linkext'>Ranglisten</a></div>\n";
319        } elseif ($can_edit) {
320            // OLZ Rangliste-hochladen-Link zeigen
321            $link .= "<div><a href='{$code_href}apps/resultate?file={$result_filename}' target='_blank' class='linkext'>Rangliste hochladen</a></div>\n";
322        }
323        if ($link == "") {
324            $link = "&nbsp;";
325        } else {
326            $link = str_replace("&", "&amp;", str_replace("&amp;", "&", $link));
327        }
328        $link = str_replace("www.solv.ch", "www.o-l.ch", $link);
329        $out .= "<div class='links'>".$link."</div>";
330
331        // Karte zeigen
332        if ($has_location) {
333            if ($location_name !== null) {
334                $location_maybe_link = $location_name;
335                if ($this->authUtils()->hasPermission('termine')) {
336                    $location_maybe_link = "<a href='{$code_href}termine/orte/{$termin_location->getId()}' class='linkmap'>{$location_name}</a>";
337                }
338                $out .= "<h3>Ort: {$location_maybe_link}</h3>";
339            } else {
340                $out .= "<h3>Ort</h3>";
341            }
342            $out .= OlzLocationMap::render([
343                'name' => $location_name ?? '',
344                'latitude' => $lat,
345                'longitude' => $lng,
346                'zoom' => 13,
347            ]);
348            if ($has_termin_location) {
349                $details = $termin_location->getDetails() ?? '';
350                $details_html = $this->htmlUtils()->renderMarkdown($details);
351                $details_html = $termin_location->replaceImagePaths($details_html);
352                $details_html = $termin_location->replaceFilePaths($details_html);
353                $out .= $details_html;
354
355                $location_image_ids = $termin_location->getImageIds();
356                if (count($location_image_ids) > 0) {
357                    $out .= "<div class='lightgallery gallery-container'>";
358                    foreach ($location_image_ids as $image_id) {
359                        $out .= "<div class='gallery-image'>";
360                        $out .= $this->imageUtils()->olzImage(
361                            'termin_locations',
362                            $termin_location->getId(),
363                            $image_id,
364                            128,
365                            'gallery[myset]'
366                        );
367                        $out .= "</div>";
368                    }
369                    $out .= "</div>";
370                }
371            }
372        }
373
374        // Reactions
375        $json_id = json_encode($id);
376        $linked_reactions = $this->htmlUtils()->getLinkedReactions($text_html);
377        $json_reactions = json_encode($linked_reactions) ?: '[]';
378        $out .= "<div id='termin-reactions'></div><script>olz.initTerminReactions({$json_id}{$json_reactions});</script>";
379
380        $out .= "</div>"; // olz-termin-detail
381        $out .= "</div>"; // content-middle
382
383        $out .= OlzFooter::render();
384
385        return $out;
386    }
387
388    protected function getTerminById(int $id): ?Termin {
389        $termin_repo = $this->entityManager()->getRepository(Termin::class);
390        return $termin_repo->findOneBy([
391            'id' => $id,
392            'on_off' => 1,
393        ]);
394    }
395
396    protected function getTimeText(?\DateTime $time): ?string {
397        if (!$time || $time->format('H:i:s') === '00:00:00') {
398            return null;
399        }
400        return $time->format('H:i');
401    }
402}