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